Last active
November 24, 2021 17:35
-
-
Save max10rogerio/33c6a3fbf7139da9296e434ba385517f to your computer and use it in GitHub Desktop.
Typescript spy mock pattern example
This file contains hidden or 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
/** | |
* global describe | |
*/ | |
namespace Command { | |
export type NotAsync = String | |
} | |
/** | |
* Generic interface basead in design pattern: https://refactoring.guru/design-patterns/command | |
* | |
* Example returning Promise type Response | |
* ```ts | |
* interface Mail extends Command<string, string>{} | |
* | |
* class MailService implements Mail { | |
execute(p: Mail.Params): Promise<Mail.Response> { | |
throw new Error("Method not implemented.") | |
} | |
} | |
* | |
* | |
* ``` | |
* --- | |
* | |
* Example returning simple type Response | |
* ```ts | |
* interface Mail extends Command<string, string, Command.NotAsync>{} | |
* | |
* class MailService implements Mail { | |
execute(p: Mail.Params): Mail.Response { | |
throw new Error("Method not implemented.") | |
} | |
} | |
* ``` | |
*/ | |
interface Command<R = any, P = any, IsAsync = Boolean> { | |
execute(p: P): IsAsync extends Boolean ? Promise<R> : R | |
} | |
namespace SendMail { | |
export type Params = { | |
mail: string | |
} | |
export type Response = { | |
sent: boolean | |
} | |
} | |
/** | |
* Use Case Mail | |
*/ | |
interface SendMail extends Command<SendMail.Response, SendMail.Params>{} | |
/** | |
* Implemetation of use case Mail | |
*/ | |
class MailService implements SendMail { | |
public async execute(p: SendMail.Params): Promise<SendMail.Response> { | |
// imagine implemetation of send email here | |
return { | |
sent: false | |
} | |
} | |
} | |
/** | |
* Imagine implemetation of controller | |
*/ | |
class MailController { | |
constructor (private readonly sendMail: SendMail) {} | |
public async handle(request: any) { | |
const emailSent = await this.sendMail.execute({ mail: request.mail }) | |
if (!emailSent.sent) { | |
return { | |
status: 400, | |
error: 'error to send email', | |
} | |
} | |
return { | |
status: 200, | |
} | |
} | |
} | |
/********************************************************* */ | |
/********************************************************* */ | |
// In Tests | |
/********************************************************* */ | |
/********************************************************* */ | |
/** | |
* Generic class for mocks | |
*/ | |
abstract class SpyAsyncMock<P = any, R = any> implements Command<R, P> { | |
params: P = '' as unknown as P | |
result: R = '' as unknown as R | |
calls = 0 | |
public call() { | |
this.calls += 1 | |
} | |
public async execute(p: P) { | |
this.params = p | |
this.call() | |
return this.result as R | |
} | |
} | |
class SendMailSpy extends SpyAsyncMock<SendMail.Params, SendMail.Response> implements SendMail {} | |
type Sut = { | |
sut: MailController | |
mailService: SendMailSpy | |
} | |
/** | |
* factory for unit tests | |
*/ | |
const makeSut = (): Sut => { | |
const mailService = new SendMailSpy() | |
const sut = new MailController(mailService); | |
return { | |
mailService, sut, | |
} | |
} | |
describe('Unit Tests', () => { | |
it('should call email service and success status', async () => { | |
// You don't need use jest.spyOn or mockImplemetation if use spy mock pattern | |
const { sut, mailService } = makeSut() | |
mailService.result = { | |
sent: true | |
} | |
await expect(sut.handle({ mail: '[email protected]' })).toBe({ status: 200 }) | |
}) | |
it('should call email service and error status', async () => { | |
// You don't need use jest.spyOn or mockImplemetation if use spy mock pattern | |
const { sut, mailService } = makeSut() | |
mailService.result = { | |
sent: false | |
} | |
await expect(sut.handle({ mail: '[email protected]' })).toBe({ status: 400, message: 'error to send email' }) | |
}) | |
}) | |
// Utils function if you don't like create class of spies. | |
const generateSpyClass = <P, R>(): SpyAsyncMock<P, R> => { | |
const t = new (class extends SpyAsyncMock<P, R> {})() | |
return t | |
} | |
/* example */ | |
const anotherMailSpy = generateSpyClass<SendMail.Params, SendMail.Response>() | |
anotherMailSpy.result = { sent: true } | |
const controllerWithSpy = new MailController(anotherMailSpy) | |
const response = controllerWithSpy.handle({ mail: 'dsdsadsa' }) | |
console.log(response) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment