diff --git a/package-lock.json b/package-lock.json index 1518f0a..288bfe7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@athenna/test", - "version": "4.17.0", + "version": "4.18.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@athenna/test", - "version": "4.17.0", + "version": "4.18.0", "license": "MIT", "dependencies": { "@japa/assert": "^2.1.0", diff --git a/package.json b/package.json index e84e143..04387bb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@athenna/test", - "version": "4.17.0", + "version": "4.18.0", "description": "The Athenna test runner. Built on top of Japa.", "license": "MIT", "author": "João Lenon ", diff --git a/src/exceptions/AfterAllHookException.ts b/src/exceptions/AfterAllHookException.ts index ee81970..ad493bc 100644 --- a/src/exceptions/AfterAllHookException.ts +++ b/src/exceptions/AfterAllHookException.ts @@ -18,7 +18,7 @@ export class AfterAllHookException extends Exception { const hook = Color.green.bold('@AfterAll') const classMethod = Color.yellow.bold(`${className}.${method}`) const message = `${Color.gray.bold.bgYellow(' MESSAGE ')}\n\n${ - error.message + error.message ? error.message : JSON.stringify(error, null, 2) }` error.message = `An exception has occurred while running the ${hook} hook in ${classMethod} method.\n\n${message}` diff --git a/src/globals/Assert.ts b/src/globals/Assert.ts index abd1314..a00f6ac 100644 --- a/src/globals/Assert.ts +++ b/src/globals/Assert.ts @@ -122,44 +122,87 @@ export {} declare module '@japa/assert' { export interface Assert { throws(fn: () => any, errType: any, message?: string): void - doesNotThrows(fn: () => any, errType: any, message?: string): void + doesNotThrow(fn: () => any, errType: any, message?: string): void rejects( fn: () => any | Promise, errType: any, message?: string ): Promise - doesNotRejects( + doesNotReject( fn: () => any | Promise, errType: any, message?: string ): Promise /** * Assert that the given mock was called. + * + * @example + * ```ts + * console.log('hello', 'world', '!') + * assert.called(console.log) // passes + * ``` */ called(mock: any): void + /** * Assert that the given mock was not called. + * + * @example + * ```ts + * assert.notCalled(console.log) // passes + * ``` */ notCalled(mock: any): void + /** * Assert that the given mock was called only once. + * + * @example + * ```ts + * console.log('hello', 'world', '!') + * assert.calledOnce(console.log) // passes + * ``` */ calledOnce(mock: any): void + /** * Assert that the given mock was called the * determined number of times. + * + * @example + * ```ts + * console.log('hello', 'world', '!') + * console.log('hello', 'world', '!') + * console.log('hello', 'world', '!') + * assert.calledTimes(console.log, 3) // passes + * ``` */ calledTimes(mock: any, times: number): void + /** * Assert that the given mock was called with the * determined arguments. + * + * @example + * ```ts + * console.log('hello', 'world', '!') + * assert.calledWith(console.log, 'hello', 'world', '!') // passes + * ``` */ calledWith(mock: any, ...args: any[]): void + /** * Assert that the given mock was not called with the * determined arguments. + * + * @example + * ```ts + * console.log('hello', 'world', '!') + * assert.notCalledWith(console.log, 'hello', 'world') // passes + * ``` */ notCalledWith(mock: any, ...args: any[]): void + /** * Assert that the given mock was called with the * arguments matching some of the given arguments. @@ -173,6 +216,7 @@ declare module '@japa/assert' { * ``` */ calledWithMatch(mock: any, ...args: any[]): void + /** * Assert that the given mock was not called with the * arguments matching some of the given arguments. @@ -186,35 +230,82 @@ declare module '@japa/assert' { * ``` */ notCalledWithMatch(mock: any, ...args: any[]): void + /** * Assert that the given mock was called only once * with the determined arguments. + * + * @example + * ```ts + * console.log('hello', 'world', '!') + * assert.calledOnceWith(console.log, 'hello', 'world', '!') // passes + * ``` */ calledOnceWith(mock: any, ...args: any[]): void + /** * Assert that the given mock was called the * determined number of times with always the determined * arguments. + * + * @example + * ```ts + * console.log('hello', 'world', '!') + * console.log('hello', 'world', '!') + * assert.calledTimesWith(console.log, 2, 'hello', 'world', '!') // passes + * ``` */ calledTimesWith(mock: any, times: number, ...args: any[]): void + /** * Assert that the given mock was called before * an other determined mock. + * + * @example + * ```ts + * console.log('hello', 'world', '!') + * console.error('hello', 'world', '!') + * assert.calledBefore(console.log, console.error) // passes + * ``` */ calledBefore(mock: any, beforeMock: any): void + /** * Assert that the given mock was not called before * an other determined mock. + * + * @example + * ```ts + * console.error('hello', 'world', '!') + * console.log('hello', 'world', '!') + * assert.notCalledBefore(console.log, console.error) // passes + * ``` */ notCalledBefore(mock: any, beforeMock: any): void + /** * Assert that the given mock was called after * an other determined mock. + * + * @example + * ```ts + * console.error('hello', 'world', '!') + * console.log('hello', 'world', '!') + * assert.calledAfter(console.log, console.error) // passes + * ``` */ calledAfter(mock: any, afterMock: any): void + /** * Assert that the given mock was not called after * an other determined mock. + * + * @example + * ```ts + * console.log('hello', 'world', '!') + * console.error('hello', 'world', '!') + * assert.notCalledAfter(console.log, console.error) // passes + * ``` */ notCalledAfter(mock: any, afterMock: any): void } diff --git a/src/mocks/Mock.ts b/src/mocks/Mock.ts index 6ca1c5c..14ea565 100644 --- a/src/mocks/Mock.ts +++ b/src/mocks/Mock.ts @@ -29,7 +29,7 @@ export class Mock { * and a method of the object. */ public static when(object: T, method: keyof T): MockBuilder { - return new MockBuilder(object, method, Mock.sandbox) + return new MockBuilder(Mock.sandbox, object, method) } /** @@ -78,8 +78,8 @@ export class Mock { * assert.isTrue({ hello: 'world', name: 'João' }, Mock.match({ hello: 'world' })) * ``` */ - public static match(value: any): Match { - return Mock.sandbox.match(value) + public static get match(): Match { + return Mock.sandbox.match } /** diff --git a/src/mocks/MockBuilder.ts b/src/mocks/MockBuilder.ts index 636b7f8..341238e 100644 --- a/src/mocks/MockBuilder.ts +++ b/src/mocks/MockBuilder.ts @@ -7,16 +7,469 @@ * file that was distributed with this source code. */ -import type { Stub } from '#src' +import { Mock, type Stub } from '#src' import type { SinonSandbox } from 'sinon' -import type { Exception } from '@athenna/common' +import { type Exception } from '@athenna/common' export class MockBuilder { + /** + * Holds sinon stub instance. + */ + private stub: Stub + + /** + * Holds the sinon sandbox instance. + */ + private sandbox: SinonSandbox + + /** + * Holds the instance of the stubbed object. + */ + private object: any + + /** + * Holds the method of the stubbed object + */ + private method: any + public constructor( - private object: any, - private method: any, - private sandbox: SinonSandbox - ) {} + sandbox: SinonSandbox, + object: any, + method: any, + stub?: Stub + ) { + if (stub) { + this.stub = stub + + return + } + + this.sandbox = sandbox + this.object = object + this.method = method + + this.stub = this.sandbox.stub(object, method).callsFake((...args) => { + return this.stub.wrappedMethod.bind(object)(...args) + }) + } + + /** + * Returns the sinon stub instance. + */ + public get() { + return this.stub + } + + /** + * Mock the method only for the provided arguments. + * This is useful to be more expressive in your + * assertions, where you can access the spy with + * the same call. It is also useful to create a + * stub that can act differently in response to + * different arguments. + * + * @example + * ```ts + * import { Mock } from '@athenna/test' + * + * Mock.when(console, 'log').withArgs('Hello').returns(10) + * + * const value = console.log('Hello') + * + * console.log(value) // 10 + * ``` + */ + public withArgs(...args: any[]) { + return new MockBuilder(null, null, null, this.stub.withArgs(...args)) + } + + /** + * Same as `withArgs()` method, but match + * any argument that is a valid string. + * + * @example + * ```ts + * import { Mock } from '@athenna/test' + * + * Mock.when(console, 'log').withStringArg().returns(10) + * + * const value = console.log('Hello') + * + * console.log(value) // 10 + * ``` + * + * * This method is an alias for: + * + * @example + * ```ts + * Mock.when(console, 'log').withArgs(Mock.match.string)... + * ``` + */ + public withStringArg() { + return this.withArgs(Mock.match.string) + } + + /** + * Same as `withArgs()` method, but match + * any argument that is a valid number. + * + * @example + * ```ts + * import { Mock } from '@athenna/test' + * + * Mock.when(console, 'log').withNumberArg().returns(10) + * + * const value = console.log(10) + * + * console.log(value) // 10 + * ``` + * + * * This method is an alias for: + * + * @example + * ```ts + * Mock.when(console, 'log').withArgs(Mock.match.number)... + * ``` + */ + public withNumberArg() { + return this.withArgs(Mock.match.number) + } + + /** + * Same as `withArgs()` method, but match + * any argument that is a valid array. + * + * @example + * ```ts + * import { Mock } from '@athenna/test' + * + * Mock.when(console, 'log').withArrayArg().returns(10) + * + * const value = console.log([10]) + * + * console.log(value) // 10 + * ``` + * + * * This method is an alias for: + * + * @example + * ```ts + * Mock.when(console, 'log').withArgs(Mock.match.array)... + * ``` + */ + public withArrayArg() { + return this.withArgs(Mock.match.array) + } + + /** + * Same as `withArgs()` method, but match + * any argument that is a valid array. + * + * @example + * ```ts + * import { Mock } from '@athenna/test' + * + * Mock.when(console, 'log').withObjectArg().returns(10) + * + * const value = console.log({}) + * + * console.log(value) // 10 + * ``` + * + * * This method is an alias for: + * + * @example + * ```ts + * Mock.when(console, 'log').withArgs(Mock.match.object)... + * ``` + */ + public withObjectArg() { + return this.withArgs(Mock.match.object) + } + + /** + * Same as `withArgs()` method, but match + * any argument that is a valid boolean. + * + * @example + * ```ts + * import { Mock } from '@athenna/test' + * + * Mock.when(console, 'log').withBooleanArg().returns(10) + * + * const value = console.log(false) + * + * console.log(value) // 10 + * ``` + * + * * This method is an alias for: + * + * @example + * ```ts + * Mock.when(console, 'log').withArgs(Mock.match.bool)... + * ``` + */ + public withBooleanArg() { + return this.withArgs(Mock.match.bool) + } + + /** + * Same as `withArgs()` method, but match + * any argument that is a valid function. + * + * @example + * ```ts + * import { Mock } from '@athenna/test' + * + * Mock.when(console, 'log').withFunctionArg().returns(10) + * + * const value = console.log(() => {}) + * + * console.log(value) // 10 + * ``` + * + * * This method is an alias for: + * + * @example + * ```ts + * Mock.when(console, 'log').withArgs(Mock.match.function)... + * ``` + */ + public withFunctionArg() { + return this.withArgs(Mock.match.func) + } + + /** + * Same as `withArgs()` method, but match + * any argument that is a valid date. + * + * @example + * ```ts + * import { Mock } from '@athenna/test' + * + * Mock.when(console, 'log').withDateArg().returns(10) + * + * const value = console.log(new Date()) + * + * console.log(value) // 10 + * ``` + * + * * This method is an alias for: + * + * @example + * ```ts + * Mock.when(console, 'log').withArgs(Mock.match.date)... + * ``` + */ + public withDateArg() { + return this.withArgs(Mock.match.date) + } + + /** + * Same as `withArgs()` method, but match + * any argument that is a valid regex. + * + * @example + * ```ts + * import { Mock } from '@athenna/test' + * + * Mock.when(console, 'log').withRegexpArg().returns(10) + * + * const value = console.log(new RegExp()) + * + * console.log(value) // 10 + * ``` + * + * * This method is an alias for: + * + * @example + * ```ts + * Mock.when(console, 'log').withArgs(Mock.match.regexp)... + * ``` + */ + public withRegexpArg() { + return this.withArgs(Mock.match.regexp) + } + + /** + * Same as `withArgs()` method, but match + * any argument that is a valid falsy. + * + * @example + * ```ts + * import { Mock } from '@athenna/test' + * + * Mock.when(console, 'log').withFalsyArg().returns(10) + * + * const value = console.log('') + * + * console.log(value) // 10 + * ``` + * + * * This method is an alias for: + * + * @example + * ```ts + * Mock.when(console, 'log').withArgs(Mock.match.falsy)... + * ``` + */ + public withFalsyArg() { + return this.withArgs(Mock.match.falsy) + } + + /** + * Same as `withArgs()` method, but match + * any argument that is a valid falsy. + * + * @example + * ```ts + * import { Mock } from '@athenna/test' + * + * Mock.when(console, 'log').withTruthyArg().returns(10) + * + * const value = console.log('') + * + * console.log(value) // 10 + * ``` + * + * * This method is an alias for: + * + * @example + * ```ts + * Mock.when(console, 'log').withArgs(Mock.match.truthy)... + * ``` + */ + public withTruthyArg() { + return this.withArgs(Mock.match.truthy) + } + + /** + * Same as `withArgs()` method, but match + * any argument. + * + * @example + * ```ts + * import { Mock } from '@athenna/test' + * + * Mock.when(console, 'log').withAnyArg().returns(10) + * + * const value = console.log('') + * + * console.log(value) // 10 + * ``` + * + * * This method is an alias for: + * + * @example + * ```ts + * Mock.when(console, 'log').withArgs(Mock.match.any)... + * ``` + */ + public withAnyArg() { + return this.withArgs(Mock.match.any) + } + + /** + * Defines the behavior of the stub on the nth + * call. Useful for testing sequential interactions. + * + * @example + * ```ts + * import { Mock } from '@athenna/test' + * + * Mock.when(console, 'log').onCall(3).returns(10) + * + * console.log('Hello') + * console.log('Hello') + * const value = console.log('Hello') + * + * console.log(value) // 10 + * ``` + */ + public onCall(number: number) { + return new MockBuilder(null, null, null, this.stub.onCall(number - 1)) + } + + /** + * Defines the behavior of the stub on the first + * call. Useful for testing sequential interactions. + * + * @example + * ```ts + * import { Mock } from '@athenna/test' + * + * Mock.when(console, 'log').onFirstCall().returns(10) + * + * const value = console.log('Hello') + * + * console.log(value) // 10 + * ``` + */ + public onFirstCall() { + return this.onCall(1) + } + + /** + * Defines the behavior of the stub on the second + * call. Useful for testing sequential interactions. + * + * @example + * ```ts + * import { Mock } from '@athenna/test' + * + * Mock.when(console, 'log').onSecondCall().returns(10) + * + * console.log('Hello') + * const value = console.log('Hello') + * + * console.log(value) // 10 + * ``` + */ + public onSecondCall() { + return this.onCall(2) + } + + /** + * Defines the behavior of the stub on the third + * call. Useful for testing sequential interactions. + * + * @example + * ```ts + * import { Mock } from '@athenna/test' + * + * Mock.when(console, 'log').onSecondCall().returns(10) + * + * console.log('Hello') + * console.log('Hello') + * const value = console.log('Hello') + * + * console.log(value) // 10 + * ``` + */ + public onThirdCall() { + return this.onCall(3) + } + + /** + * Mock a property making it return + * it own `this` property. + * + * @example + * ```ts + * import { Mock } from '@athenna/test' + * + * const mock = Mock.when(console, 'log').returnThis().get() + * + * if (mock() === console) { + * return + * } + * ``` + */ + public returnThis() { + this.stub = this.stub.returnsThis() + + return this + } /** * Mock a property changing it value. @@ -25,17 +478,15 @@ export class MockBuilder { * ```ts * import { Mock } from '@athenna/test' * - * const mock = Mock.when(console, 'log').value(() => {}) + * const mock = Mock.when(console, 'log').value(() => {}).get() * * mock.called // false * ``` */ - public value(value: T): Stub { - const stub = this.sandbox.stub(this.object, this.method) - - stub.value(value) + public value(value: any) { + this.stub = this.stub.value(value) - return stub + return this } /** @@ -45,17 +496,15 @@ export class MockBuilder { * ```ts * import { Mock } from '@athenna/test' * - * const mock = Mock.when(console, 'log').return('Hello World') + * const mock = Mock.when(console, 'log').return('Hello World').get() * * mock.called // false * ``` */ - public return(value: T): Stub { - const stub = this.sandbox.stub(this.object, this.method) + public return(value: any) { + this.stub = this.stub.returns(value) - stub.returns(value) - - return stub + return this } /** @@ -70,12 +519,10 @@ export class MockBuilder { * mock.called // false * ``` */ - public throw(value: string | Error | Exception): Stub { - const stub = this.sandbox.stub(this.object, this.method) - - stub.throws(value) + public throw(value: string | Error | Exception): MockBuilder { + this.stub = this.stub.throws(value) - return stub + return this } /** @@ -85,17 +532,15 @@ export class MockBuilder { * ```ts * import { Mock } from '@athenna/test' * - * const mock = Mock.when(console, 'log').resolve('Hello World') + * const mock = Mock.when(console, 'log').resolve('Hello World').get() * * mock.called // false * ``` */ - public resolve(value: T): Stub { - const stub = this.sandbox.stub(this.object, this.method) + public resolve(value: any) { + this.stub = this.stub.resolves(value) - stub.resolves(value) - - return stub + return this } /** @@ -105,16 +550,14 @@ export class MockBuilder { * ```ts * import { Mock } from '@athenna/test' * - * const mock = Mock.when(console, 'log').reject('Hello World') + * const mock = Mock.when(console, 'log').reject('Hello World').get() * * mock.called // false * ``` */ - public reject(value: T): Stub { - const stub = this.sandbox.stub(this.object, this.method) - - stub.rejects(value) + public reject(value: T): MockBuilder { + this.stub = this.stub.rejects(value) - return stub + return this } } diff --git a/src/types/Match.ts b/src/types/Match.ts index c3deca0..146eab2 100644 --- a/src/types/Match.ts +++ b/src/types/Match.ts @@ -7,6 +7,6 @@ * file that was distributed with this source code. */ -import type { SinonMatcher } from 'sinon' +import type { SinonMatch } from 'sinon' -export type Match = SinonMatcher +export type Match = SinonMatch diff --git a/tests/fixtures/UserService.ts b/tests/fixtures/UserService.ts index 9c4d363..07ae8c9 100644 --- a/tests/fixtures/UserService.ts +++ b/tests/fixtures/UserService.ts @@ -18,7 +18,7 @@ export class UserService { ] } - public async findById(id: number) { + public async findById(id?: number) { const users = await this.find() return users.find(user => user.id === id) diff --git a/tests/unit/AfterAllExceptionTest.ts b/tests/unit/AfterAllExceptionTest.ts index c6bed5d..088b1d5 100644 --- a/tests/unit/AfterAllExceptionTest.ts +++ b/tests/unit/AfterAllExceptionTest.ts @@ -15,7 +15,7 @@ export default class AfterAllExceptionTest { @BeforeAll() public async beforeAll() { - this.processExit = Mock.when(process, 'exit').return(undefined) + this.processExit = Mock.when(process, 'exit').return(undefined).get() } @AfterAll() diff --git a/tests/unit/AfterEachExceptionTest.ts b/tests/unit/AfterEachExceptionTest.ts index 80abd70..07d2503 100644 --- a/tests/unit/AfterEachExceptionTest.ts +++ b/tests/unit/AfterEachExceptionTest.ts @@ -15,7 +15,7 @@ export default class AfterEachExceptionTest { @BeforeAll() public async beforeAll() { - this.processExit = Mock.when(process, 'exit').return(undefined) + this.processExit = Mock.when(process, 'exit').return(undefined).get() } @AfterEach() diff --git a/tests/unit/BeforeAllExceptionTest.ts b/tests/unit/BeforeAllExceptionTest.ts index da0f21c..df66c2e 100644 --- a/tests/unit/BeforeAllExceptionTest.ts +++ b/tests/unit/BeforeAllExceptionTest.ts @@ -15,7 +15,7 @@ export default class BeforeAllExceptionTest { @BeforeAll() public async beforeAll() { - this.processExit = Mock.when(process, 'exit').return(undefined) + this.processExit = Mock.when(process, 'exit').return(undefined).get() } @BeforeAll() diff --git a/tests/unit/BeforeEachExceptionTest.ts b/tests/unit/BeforeEachExceptionTest.ts index b2ffda5..834cca0 100644 --- a/tests/unit/BeforeEachExceptionTest.ts +++ b/tests/unit/BeforeEachExceptionTest.ts @@ -15,7 +15,7 @@ export default class BeforeEachExceptionTest { @BeforeAll() public async beforeAll() { - this.processExit = Mock.when(process, 'exit').return(undefined) + this.processExit = Mock.when(process, 'exit').return(undefined).get() } @BeforeEach() @@ -35,6 +35,7 @@ export default class BeforeEachExceptionTest { @Test() public async shouldLogAnExceptionWhenBeforeEachHookFails({ assert }: Context) { + console.log(this.processExit) assert.isTrue(this.processExit.calledTwice) assert.isTrue(this.processExit.calledWith(1)) } diff --git a/tests/unit/mocks/MockTest.ts b/tests/unit/mocks/MockTest.ts index a918369..2a3cf5b 100644 --- a/tests/unit/mocks/MockTest.ts +++ b/tests/unit/mocks/MockTest.ts @@ -67,7 +67,7 @@ export default class MockTest { public async shouldBeAbleToMockObjectMethodsToReturnValue({ assert }: Context) { const userService = new UserService() - const mock = Mock.when(userService, 'findById').return({ id: 2 }) + const mock = Mock.when(userService, 'findById').return({ id: 2 }).get() userService.findById(1) @@ -78,7 +78,7 @@ export default class MockTest { public async shouldBeAbleToMockObjectMethodsToResolveAReturnValue({ assert }: Context) { const userService = new UserService() - const mock = Mock.when(userService, 'findById').resolve({ id: 2 }) + const mock = Mock.when(userService, 'findById').resolve({ id: 2 }).get() await userService.findById(1) @@ -129,8 +129,8 @@ export default class MockTest { Mock.restoreAll() - await assert.doesNotRejects(() => userService.find()) - await assert.doesNotRejects(() => userService.findById(1)) + await assert.doesNotReject(() => userService.find()) + await assert.doesNotReject(() => userService.findById(1)) } @Test() @@ -146,20 +146,24 @@ export default class MockTest { Mock.restore(userService.find) Mock.restore(userService.findById) - await assert.doesNotRejects(() => userService.find()) - await assert.doesNotRejects(() => userService.findById(1)) + await assert.doesNotReject(() => userService.find()) + await assert.doesNotReject(() => userService.findById(1)) } @Test() public async shouldBeAbleToRestoreASingleMockedProperty({ assert }: Context) { const userService = new UserService() - const mockFind = Mock.when(userService, 'find').value(() => { - throw new Error('ERROR_MOCK') - }) - const mockFindById = Mock.when(userService, 'findById').value(() => { - throw new Error('ERROR_MOCK') - }) + const mockFind = Mock.when(userService, 'find') + .value(() => { + throw new Error('ERROR_MOCK') + }) + .get() + const mockFindById = Mock.when(userService, 'findById') + .value(() => { + throw new Error('ERROR_MOCK') + }) + .get() await assert.rejects(() => userService.find(), Error) await assert.rejects(() => userService.findById(1), Error) @@ -167,7 +171,223 @@ export default class MockTest { Mock.restore(mockFind) Mock.restore(mockFindById) - await assert.doesNotRejects(() => userService.find()) - await assert.doesNotRejects(() => userService.findById(1)) + await assert.doesNotReject(() => userService.find()) + await assert.doesNotReject(() => userService.findById(1)) + } + + @Test() + public async shouldBeAbleToMockAMethodOnlyWhenCalledWithDeterminedArgs({ assert }: Context) { + const userService = new UserService() + + Mock.when(userService, 'findById').withArgs(2).resolve({ id: 2 }).withArgs(3).resolve({ id: 3 }) + + assert.deepEqual(await userService.findById(1), { id: 1, name: 'João Lenon', email: 'lenon@athenna.io' }) + assert.deepEqual(await userService.findById(2), { id: 2 }) + assert.deepEqual(await userService.findById(3), { id: 3 }) + assert.deepEqual(await userService.findById(), undefined) + } + + @Test() + public async shouldBeAbleToMockAMethodOnlyWhenCalledWithDeterminedArgsChangingItBehavior({ assert }: Context) { + const userService = new UserService() + + Mock.when(userService, 'findById').withArgs(2).resolve({ id: 2 }).withArgs(3).return({ id: 3 }) + + assert.deepEqual(await userService.findById(1), { id: 1, name: 'João Lenon', email: 'lenon@athenna.io' }) + assert.deepEqual(await userService.findById(2), { id: 2 }) + assert.deepEqual(userService.findById(3), { id: 3 }) + assert.deepEqual(await userService.findById(), undefined) + } + + @Test() + public async shouldBeAbleToMockAMethodOnlyOnSecondAndThirdCall({ assert }: Context) { + const userService = new UserService() + + Mock.when(userService, 'findById').onSecondCall().resolve({ id: 2 }).onThirdCall().return({ id: 3 }) + + assert.deepEqual(await userService.findById(1), { id: 1, name: 'João Lenon', email: 'lenon@athenna.io' }) + assert.deepEqual(await userService.findById(2), { id: 2 }) + assert.deepEqual(userService.findById(3), { id: 3 }) + assert.deepEqual(await userService.findById(), undefined) + } + + @Test() + public async shouldBeAbleToMockAMethodOnlyOnFourthCall({ assert }: Context) { + const userService = new UserService() + + Mock.when(userService, 'findById').onCall(4).resolve({ id: 4 }) + + assert.deepEqual(await userService.findById(1), { id: 1, name: 'João Lenon', email: 'lenon@athenna.io' }) + assert.deepEqual(await userService.findById(1), { id: 1, name: 'João Lenon', email: 'lenon@athenna.io' }) + assert.deepEqual(await userService.findById(1), { id: 1, name: 'João Lenon', email: 'lenon@athenna.io' }) + assert.deepEqual(await userService.findById(1), { id: 4 }) + } + + @Test() + public async shouldBeAbleToMockOnlyWhenArgIsAString({ assert }: Context) { + const userService = new UserService() + + Mock.when(userService, 'findById').withStringArg().resolve({ id: 2 }) + + // eslint-disable-next-line + // @ts-ignore + assert.deepEqual(await userService.findById('1'), { id: 2 }) + assert.deepEqual(await userService.findById(1), { id: 1, name: 'João Lenon', email: 'lenon@athenna.io' }) + } + + @Test() + public async shouldBeAbleToMockOnlyWhenArgIsANumber({ assert }: Context) { + const userService = new UserService() + + Mock.when(userService, 'findById').withNumberArg().resolve({ id: 2 }) + + // eslint-disable-next-line + // @ts-ignore + assert.deepEqual(await userService.findById(1), { id: 2 }) + assert.deepEqual(await userService.findById(), undefined) + } + + @Test() + public async shouldBeAbleToMockOnlyWhenArgIsAnObject({ assert }: Context) { + const userService = new UserService() + + Mock.when(userService, 'findById').withObjectArg().resolve({ id: 2 }) + + // eslint-disable-next-line + // @ts-ignore + assert.deepEqual(await userService.findById({}), { id: 2 }) + assert.deepEqual(await userService.findById(), undefined) + } + + @Test() + public async shouldBeAbleToMockOnlyWhenArgIsAnArray({ assert }: Context) { + const userService = new UserService() + + Mock.when(userService, 'findById').withArrayArg().resolve({ id: 2 }) + + // eslint-disable-next-line + // @ts-ignore + assert.deepEqual(await userService.findById([]), { id: 2 }) + assert.deepEqual(await userService.findById(), undefined) + } + + @Test() + public async shouldBeAbleToMockOnlyWhenArgIsADate({ assert }: Context) { + const userService = new UserService() + + Mock.when(userService, 'findById').withDateArg().resolve({ id: 2 }) + + // eslint-disable-next-line + // @ts-ignore + assert.deepEqual(await userService.findById(new Date()), { id: 2 }) + assert.deepEqual(await userService.findById(), undefined) + } + + @Test() + public async shouldBeAbleToMockOnlyWhenArgIsARegexp({ assert }: Context) { + const userService = new UserService() + + Mock.when(userService, 'findById').withRegexpArg().resolve({ id: 2 }) + + // eslint-disable-next-line + // @ts-ignore + assert.deepEqual(await userService.findById(new RegExp()), { id: 2 }) + assert.deepEqual(await userService.findById(), undefined) + } + + @Test() + public async shouldBeAbleToMockOnlyWhenArgIsABoolean({ assert }: Context) { + const userService = new UserService() + + Mock.when(userService, 'findById').withBooleanArg().resolve({ id: 2 }) + + // eslint-disable-next-line + // @ts-ignore + assert.deepEqual(await userService.findById(false), { id: 2 }) + // eslint-disable-next-line + // @ts-ignore + assert.deepEqual(await userService.findById(true), { id: 2 }) + assert.deepEqual(await userService.findById(), undefined) + } + + @Test() + public async shouldBeAbleToMockOnlyWhenArgIsAFunction({ assert }: Context) { + const userService = new UserService() + + Mock.when(userService, 'findById').withFunctionArg().resolve({ id: 2 }) + + // eslint-disable-next-line + // @ts-ignore + assert.deepEqual(await userService.findById(() => {}), { id: 2 }) + assert.deepEqual(await userService.findById(), undefined) + } + + @Test() + public async shouldBeAbleToMockOnlyWhenExistsAnyArg({ assert }: Context) { + const userService = new UserService() + + Mock.when(userService, 'findById').withAnyArg().resolve({ id: 2 }) + + // eslint-disable-next-line + // @ts-ignore + assert.deepEqual(await userService.findById(null), { id: 2 }) + assert.deepEqual(await userService.findById(), undefined) + } + + @Test() + public async shouldBeAbleToMockOnlyWhenValueIsTruthy({ assert }: Context) { + const userService = new UserService() + + Mock.when(userService, 'findById').withTruthyArg().resolve({ id: 2 }) + + // eslint-disable-next-line + // @ts-ignore + assert.deepEqual(await userService.findById(true), { id: 2 }) + assert.deepEqual(await userService.findById(), undefined) + } + + @Test() + public async shouldBeAbleToMockOnlyWhenValueIsFalsy({ assert }: Context) { + const userService = new UserService() + + Mock.when(userService, 'findById').withFalsyArg().resolve({ id: 2 }) + + // eslint-disable-next-line + // @ts-ignore + assert.deepEqual(await userService.findById(false), { id: 2 }) + assert.deepEqual(await userService.findById(), undefined) + } + + @Test() + public async shouldBeAbleToMergeWithArgsAndOnCall({ assert }: Context) { + const userService = new UserService() + + Mock.when(userService, 'findById') + .withArgs(1) + .onFirstCall() + .resolve({ id: 2 }) + .onSecondCall() + .resolve({ id: 3 }) + .withArgs(3) + .resolve({ id: 4 }) + + assert.deepEqual(await userService.findById(1), { id: 2 }) + assert.deepEqual(await userService.findById(1), { id: 3 }) + assert.deepEqual(await userService.findById(1), { + id: 1, + name: 'João Lenon', + email: 'lenon@athenna.io' + }) + assert.deepEqual(await userService.findById(2), undefined) + assert.deepEqual(await userService.findById(3), { id: 4 }) + } + + @Test() + public async shouldBeAbleToReturnTheThisPropertyInAMock({ assert }: Context) { + const userService = new UserService() + + Mock.when(userService, 'findById').returnThis() + + assert.deepEqual(await userService.findById(1), userService) } }