Skip to content

Commit

Permalink
v0.0.1
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielHreben committed Oct 26, 2020
1 parent c5df9e8 commit cabbd68
Show file tree
Hide file tree
Showing 12 changed files with 5,328 additions and 2 deletions.
8 changes: 8 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# EditorConfig is awesome: http://EditorConfig.org

[*]
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
48 changes: 48 additions & 0 deletions .eslintrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
parser: '@typescript-eslint/parser'

parserOptions:
ecmaVersion: 2018
sourceType: 'module'
project: './tsconfig.json'

plugins:
- simple-import-sort
- filenames

ignorePatterns:
- coverage
- dist
- node_modules

extends:
- 'eslint:recommended'
- 'plugin:node/recommended'
- 'plugin:jest/recommended'
- 'plugin:@typescript-eslint/eslint-recommended'
- 'plugin:@typescript-eslint/recommended'
- 'plugin:prettier/recommended'
- 'prettier/@typescript-eslint'

rules:
'@typescript-eslint/explicit-module-boundary-types': 0
'@typescript-eslint/no-namespace': 0
'node/no-unsupported-features/es-syntax': 0
'node/no-unpublished-import': 0
'node/no-missing-import': [
2,
{ tryExtensions: ['.js', '.json', '.ts', '.d.ts'] }
]
'@typescript-eslint/explicit-function-return-type': 0
'@typescript-eslint/no-explicit-any': 0
'@typescript-eslint/no-non-null-assertion': 0
'@typescript-eslint/no-use-before-define': 0
'@typescript-eslint/no-unused-vars': [
2,
{ ignoreRestSiblings: true }
]
'@typescript-eslint/no-floating-promises': 2
'@typescript-eslint/no-require-imports': 2
'no-empty-pattern': 0
'no-console': 2
'simple-import-sort/sort': 2
'filenames/match-regex': [2, '^[0-9a-z-.]+$']
20 changes: 20 additions & 0 deletions .github/workflows/branch-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Build

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12
- run: yarn --frozen-lockfile
- run: yarn lint:check
- run: yarn build
- run: yarn test
51 changes: 51 additions & 0 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages

name: Node.js Package

on:
release:
types: [created]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12
- run: yarn --frozen-lockfile
- run: yarn lint:check
- run: yarn build
- run: yarn test

publish-npm:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12
registry-url: https://registry.npmjs.org/
- run: yarn --frozen-lockfile
- run: yarn build
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.npm_token}}

publish-gpr:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12
registry-url: https://npm.pkg.github.com/
- run: yarn --frozen-lockfile
- run: yarn build
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}
6 changes: 6 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"parser": "typescript",
"printWidth": 120,
"singleQuote": true,
"arrowParens": "always"
}
49 changes: 47 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,47 @@
# jest-mock-http-server
Test your http clients with jest
# jest-mock-server
Test your http clients with jest:
* use Koa API to define http handlers
* use Jest API to define asserts

# Usage example
```typescript
import { MockServer } from 'jest-mock-server';
import fetch from 'node-fetch';

describe('Testing node-fetch HTTP client', () => {
const server = new MockServer();

beforeAll(() => server.start());
afterAll(() => server.stop());
beforeEach(() => server.reset());

it('Receives a status over the network', async () => {
const route = server
.get('/')
// Look ma, plain Jest API!
.mockImplementationOnce((ctx) => {
// ...and plain Koa API
ctx.status = 200;
})
.mockImplementationOnce((ctx) => {
ctx.status = 201;
});

// Since we did not passed any port into server constructor, server was started at random free port
const url = server.getURL();

const res1 = await fetch(url);
expect(res1.status).toBe(200);

const res2 = await fetch(url);
expect(res2.status).toBe(201);

const res3 = await fetch(url);
expect(res3.status).toBe(404);

expect(route).toHaveBeenCalledTimes(3); // Yep, jest API again
});
});

```

8 changes: 8 additions & 0 deletions jest.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"preset": "ts-jest",
"testEnvironment": "node",
"collectCoverage": true,
"testMatch": [
"**/**/*.test.ts"
]
}
57 changes: 57 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"name": "jest-mock-server",
"version": "0.0.1",
"description": "Test your http clients with jest",
"main": "dist/src/server.js",
"types": "dist/src/server.d.ts",
"repository": "[email protected]:DanielHreben/jest-mock-server.git",
"author": "Daniel Hreben <[email protected]>",
"license": "MIT",
"private": false,
"files": [
"dist/src"
],
"tags": [
"jest",
"mock",
"http",
"server",
"plugin"
],
"scripts": {
"build": "rm -rf ./dist; tsc",
"lint:check": "eslint . --ext .ts",
"lint": "yarn lint:check --fix",
"test": "jest"
},
"devDependencies": {
"@types/koa-bodyparser": "^4.3.0",
"@types/koa-router": "^7.4.1",
"@types/node": "^14.14.3",
"@types/node-fetch": "^2.5.7",
"@typescript-eslint/eslint-plugin": "^4.3.0",
"@typescript-eslint/parser": "^4.3.0",
"eslint": "^7.10.0",
"eslint-config-prettier": "^6.12.0",
"eslint-plugin-filenames": "^1.3.2",
"eslint-plugin-jest": "^24.0.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-simple-import-sort": "^5.0.3",
"jest": "^26.4.2",
"node-fetch": "^2.6.1",
"prettier": "^2.1.2",
"ts-jest": "^26.4.1",
"typescript": "^4.0.3"
},
"dependencies": {
"@types/jest": "^26.0.14",
"@types/koa": "^2.11.6",
"koa": "^2.13.0",
"koa-bodyparser": "^4.3.0",
"koa-router": "^9.4.0"
},
"peerDependencies": {
"jest": "*"
}
}
138 changes: 138 additions & 0 deletions src/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { createServer, RequestListener, Server } from 'http';
import Koa from 'koa';
import buildBodyParser from 'koa-bodyparser';
import Router from 'koa-router';
import { AddressInfo } from 'net';
import { URL } from 'url';

interface Config {
buildApp?: () => Koa;
port?: number;
}

type Path = string | RegExp;

function buildDefaultApp() {
const app = new Koa();

const bodyParser = buildBodyParser({
extendTypes: {
json: ['application/json'],
},
});

app.use(bodyParser);

return app;
}

export class MockServer {
private router!: Router;
private server?: Server;
private requestListener!: RequestListener;

constructor(private config: Config = {}) {
this.init();
}

public async start(): Promise<MockServer> {
const server = createServer((req, res) => this.requestListener(req, res));

await new Promise((resolve, reject) =>
server
.on('error', reject)
.on('listening', resolve)
.listen({ port: this.config.port || 0 })
);

this.server = server;
return this;
}

public get(path: Path) {
return this.mockPath('get', path);
}

public head(path: Path) {
return this.mockPath('head', path);
}

public post(path: Path) {
return this.mockPath('post', path);
}

public put(path: Path) {
return this.mockPath('put', path);
}

public delete(path: Path) {
return this.mockPath('delete', path);
}

public options(path: Path) {
return this.mockPath('options', path);
}

public trace(path: Path) {
return this.mockPath('trace', path);
}

public patch(path: Path) {
return this.mockPath('patch', path);
}

public all(path: Path) {
const mock = this.getDefaultMock();
this.router.all(path, mock);
return mock;
}

public getURL(): URL {
const server = this.server;

if (!server) {
throw new Error('Server is not started');
}

const { port } = server.address() as AddressInfo;
const host = 'http://localhost';
return new URL(`${host}:${port}`);
}

public async stop(): Promise<MockServer> {
const server = this.server;

if (!server) {
return this;
}

await new Promise((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));
});

return this;
}

public reset(): MockServer {
this.init();
return this;
}

private init() {
this.router = new Router();
const app = this.config.buildApp?.() || buildDefaultApp();
app.use(this.router.routes());
this.requestListener = app.callback();
}

private mockPath(method: string, path: Path) {
const mock = this.getDefaultMock();
this.router.register(path, [method], mock);

return mock;
}

private getDefaultMock() {
return jest.fn<ReturnType<Koa.Middleware>, Parameters<Koa.Middleware>>((ctx, next) => next());
}
}
Loading

0 comments on commit cabbd68

Please sign in to comment.