Skip to content

Instantly share code, notes, and snippets.

@max10rogerio
Last active November 24, 2021 17:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save max10rogerio/33c6a3fbf7139da9296e434ba385517f to your computer and use it in GitHub Desktop.
Save max10rogerio/33c6a3fbf7139da9296e434ba385517f to your computer and use it in GitHub Desktop.
Typescript spy mock pattern example
/**
* 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: 'max@max.com' })).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: 'max@max.com' })).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