-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
implement jest and sinon versions of mocks
- Loading branch information
0 parents
commit 14d9647
Showing
13 changed files
with
328 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
node_modules | ||
yarn.lock | ||
*.js | ||
*.d.ts | ||
!jest.config.js | ||
!.eslintrc.js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# proxy-mocks | ||
|
||
Generate mocks for any class or object. | ||
|
||
## Example | ||
|
||
```typescript | ||
// import { IMock, Mock } from 'proxy-mocks/jest'; | ||
import { IMock, Mock } from "proxy-mocks/sinon"; | ||
import Dependency from "./dependency"; | ||
import Implementation from "./implementation"; | ||
|
||
describe("Implementation", () => { | ||
let dependency: IMock<Dependency>; | ||
|
||
let implementation: Implementation; | ||
|
||
beforeEach(() => { | ||
dependency = Mock.of(Dependency); | ||
|
||
implementation = new Implementation(dependency); | ||
}); | ||
|
||
test("your test", () => { | ||
dependency.someMethod.returns("your result"); | ||
|
||
const result = implementation.anotherMethod(); | ||
|
||
expect(result).toEqual("your result"); | ||
}); | ||
}); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{ | ||
"$schema": "https://dprint.dev/schemas/v0.json", | ||
"projectType": "openSource", | ||
"incremental": true, | ||
"typescript": { | ||
"indentWidth": 2 | ||
}, | ||
"json": { | ||
}, | ||
"markdown": { | ||
}, | ||
"includes": ["**/*.{ts,tsx,js,jsx,json,md}"], | ||
"excludes": [ | ||
"**/node_modules", | ||
"**/*-lock.json", | ||
"src/**/*.js", | ||
"**/*.d.ts" | ||
], | ||
"plugins": [ | ||
"https://plugins.dprint.dev/typescript-0.44.0.wasm", | ||
"https://plugins.dprint.dev/json-0.10.1.wasm", | ||
"https://plugins.dprint.dev/markdown-0.6.2.wasm" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
{ | ||
"name": "proxy-mocks", | ||
"version": "0.1.0", | ||
"description": "Provide mocks utilizing the Proxy API", | ||
"main": "index.js", | ||
"repository": "https://github.com/maxjoehnk/proxy-mocks", | ||
"author": "Max Jöhnk <[email protected]>", | ||
"license": "MIT", | ||
"scripts": { | ||
"clean": "rimraf '*.{d.ts,js}'", | ||
"prebuild": "npm run clean", | ||
"build": "tsc --outDir .", | ||
"test": "jest", | ||
"lint": "eslint src --ext .ts", | ||
"prepare": "npm run check", | ||
"prepack": "npm run check", | ||
"format": "dprint fmt", | ||
"format.check": "dprint check", | ||
"check": "npm run build && npm run lint && npm run test" | ||
}, | ||
"files": [ | ||
"README.md", | ||
"jest.js", | ||
"sinon.js", | ||
"mock.js", | ||
"index.js" | ||
], | ||
"peerDependencies": { | ||
"jest": "^26.6.3", | ||
"sinon": "^10.0.1" | ||
}, | ||
"devDependencies": { | ||
"@types/jest": "^26.0.22", | ||
"@types/sinon": "^9.0.11", | ||
"@typescript-eslint/eslint-plugin": "^4.21.0", | ||
"@typescript-eslint/parser": "^4.21.0", | ||
"eslint": "^7.23.0", | ||
"jest": "^26.6.3", | ||
"rimraf": "^3.0.2", | ||
"sinon": "^10.0.1", | ||
"ts-jest": "^26.5.4", | ||
"typescript": "^4.2.4" | ||
}, | ||
"jest": { | ||
"moduleFileExtensions": [ | ||
"ts", | ||
"js", | ||
"json" | ||
], | ||
"testMatch": [ | ||
"<rootDir>/src/**/*.test.ts" | ||
], | ||
"preset": "ts-jest", | ||
"globals": { | ||
"ts-jest": { | ||
"tsconfig": "tsconfig.test.json" | ||
} | ||
} | ||
}, | ||
"eslintConfig": { | ||
"root": true, | ||
"parser": "@typescript-eslint/parser", | ||
"plugins": [ | ||
"@typescript-eslint" | ||
], | ||
"extends": [ | ||
"eslint:recommended", | ||
"plugin:@typescript-eslint/recommended" | ||
], | ||
"rules": { | ||
"@typescript-eslint/no-explicit-any": 0, | ||
"@typescript-eslint/ban-types": 0 | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * as jest from "./jest"; | ||
export * as sinon from "./sinon"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { Mock } from "./jest"; | ||
import { TestServiceToMock } from "./test-utils"; | ||
|
||
describe("Jest Mocks", () => { | ||
test("accessing a method should create a stub for it", () => { | ||
const mock = Mock.of(TestServiceToMock); | ||
|
||
const isStub = jest.isMockFunction(mock.someMethod); | ||
|
||
expect(isStub).toBe(true); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { createMockImplementationWithStubFunction, MockableObject, MockPrototype } from "./mock"; | ||
|
||
import * as base from "./mock"; | ||
|
||
/** | ||
* Mocked object with stubs generated with jest | ||
*/ | ||
export type IMock<TObject extends MockableObject> = base.IMock<TObject, jest.Mock<TObject>>; | ||
|
||
export const Mock: MockPrototype<jest.Mock> = createMockImplementationWithStubFunction(jest.fn); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
export type RecursivePartial<T> = Partial< | ||
{ | ||
[key in keyof T]: T[key] extends (...a: Array<infer U>) => unknown | ||
? (...a: Array<U>) => RecursivePartial<ReturnType<T[key]>> | ReturnType<T[key]> // tslint:disable-line | ||
: T[key] extends Array<unknown> ? Array<RecursivePartial<T[key][number]>> | ||
: RecursivePartial<T[key]> | T[key]; | ||
} | ||
>; | ||
|
||
/** | ||
* Mock object with generated stubs. | ||
* | ||
* There is no guarantee whether a property is actually a stub or not as it can be overridden at creation time. | ||
*/ | ||
export type IMock<TObject extends MockableObject, TStub> = TObject & { [P in keyof TObject]: TStub }; | ||
|
||
/** | ||
* Generate new mock class using given {@param stubFunction} to generate stubs | ||
*/ | ||
export function createMockImplementationWithStubFunction<TStub>(stubFunction: () => TStub): MockPrototype<TStub> { | ||
return class Mock { | ||
/** | ||
* Generate a new mock for the given class. | ||
* | ||
* @param clazz the class to generate a mock for. | ||
* @param overrides can be used to set properties. They will not be replaced with stubs. | ||
*/ | ||
public static of<TObject extends MockableObject>( | ||
clazz: Clazz<TObject> = null, | ||
overrides: RecursivePartial<TObject> = {}, | ||
): IMock<TObject, TStub> { | ||
return new Proxy<IMock<TObject, TStub>>(overrides as any, { | ||
get(target: IMock<TObject, TStub>, key: PropertyKey) { | ||
// Angular tries to create a Set of a provided value via the following code | ||
// | ||
// ```javascript | ||
// var QUOTED_KEYS = '$quoted$'; | ||
// var quotedSet = new Set(map && map[QUOTED_KEYS]); | ||
// ``` | ||
// | ||
// This fails when $quoted$ is a stub, so we explicitly return undefined here | ||
if (key === "$quoted$") { | ||
return undefined; | ||
} | ||
// TODO: allow configuration of Mock as a Promise | ||
if (key === "then") { | ||
return undefined; | ||
} | ||
const name: keyof TObject = key as any; | ||
if (target[name] === undefined) { | ||
target[name] = stubFunction() as any; | ||
} | ||
return target[name]; | ||
}, | ||
getPrototypeOf(): MockableObject | null { | ||
return clazz?.prototype ?? null; | ||
}, | ||
}); | ||
} | ||
|
||
/** | ||
* Generate new mock without setting the prototype (for e.g. a Typescript interface) | ||
* | ||
* {@param overrides} can be used to set properties. They will not be replaced with stubs. | ||
*/ | ||
public static with<TObject extends MockableObject>( | ||
overrides: RecursivePartial<TObject> = {}, | ||
): IMock<TObject, TStub> { | ||
return Mock.of<TObject>(null, overrides); | ||
} | ||
}; | ||
} | ||
|
||
interface Clazz<T> extends Function { | ||
new(...args: any[]): T; | ||
} | ||
|
||
export interface MockPrototype<TStub> { | ||
/** | ||
* Generate a new mock for the given class. | ||
* | ||
* @param clazz the class to generate a mock for. | ||
* @param overrides can be used to set properties. They will not be replaced with stubs. | ||
*/ | ||
of<TObject extends MockableObject>( | ||
clazz: Clazz<TObject>, | ||
overrides?: RecursivePartial<TObject>, | ||
): IMock<TObject, TStub>; | ||
|
||
/** | ||
* Generate new mock without setting the prototype (for e.g. a Typescript interface) | ||
* | ||
* {@param overrides} can be used to set properties. They will not be replaced with stubs. | ||
*/ | ||
with<TObject extends MockableObject>(overrides: RecursivePartial<TObject>): IMock<TObject, TStub>; | ||
} | ||
|
||
export type MockableObject = object; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { SinonStub } from "sinon"; | ||
import { Mock } from "./sinon"; | ||
import { TestServiceToMock } from "./test-utils"; | ||
|
||
describe("Sinon Mocks", () => { | ||
test("accessing a method should create a stub for it", () => { | ||
const mock = Mock.of(TestServiceToMock); | ||
|
||
const isStub = isSinonStub(mock.someMethod); | ||
|
||
expect(isStub).toBe(true); | ||
}); | ||
}); | ||
|
||
function isSinonStub(stub: SinonStub): boolean { | ||
// implementation detail of sinon | ||
// sinon sets the isSinonProxy to true when it creates a stub | ||
return (stub as any).isSinonProxy; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { SinonStub, stub } from "sinon"; | ||
import { createMockImplementationWithStubFunction, MockPrototype } from "./mock"; | ||
|
||
import * as base from "./mock"; | ||
|
||
/** | ||
* Mock object with generated stubs. | ||
* | ||
* There is no guarantee whether a property is actually a stub or not as it can be overridden at creation time. | ||
*/ | ||
export type IMock<TObject extends base.MockableObject> = base.IMock<TObject, SinonStub>; | ||
|
||
export const Mock: MockPrototype<SinonStub> = createMockImplementationWithStubFunction(stub); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export class TestServiceToMock { | ||
someMethod(arg: string): string { | ||
return `transformed ${arg}`; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
{ | ||
"compilerOptions": { | ||
"lib": [ | ||
"es5", | ||
"es2015.proxy" | ||
], | ||
"strict": true, | ||
"strictNullChecks": false, | ||
"alwaysStrict": true, | ||
"declaration": true, | ||
"target": "es2015", | ||
"allowJs": false, | ||
"esModuleInterop": true, | ||
"module": "es2020", | ||
"moduleResolution": "node" | ||
}, | ||
"include": [ | ||
"src/**/*.ts" | ||
], | ||
"exclude": [ | ||
"src/**/*.test.ts" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"extends": "./tsconfig.json", | ||
"compilerOptions": { | ||
"noEmit": true | ||
}, | ||
"include": [ | ||
"src/**/*.ts" | ||
] | ||
} |