DEV Community

Cover image for Mocking Interface with jest-mock-extended
Gabriel Valin
Gabriel Valin

Posted on

9

Mocking Interface with jest-mock-extended

Objetivo

Se você está cansado de mockar classes concretas, criar classes ou funções para usar de mock, essas duas funções abaixo vão te ajudar de uma maneira bem inteligente e prática.

Situação

Precisamos testar um serviço que vai nos retornar uma lista de usuários.

Fluxo do sistema

flowsystem

Testes

Muitas das vezes, criamos algum tipo de módulo/objeto para mockar dependências que seguem um "contrato" que são injetadas em um tipo de serviço. Quando usamos o princípio de Interface Segregation podemos mockar diretamente a INTERFACE usando duas funcionalidades do pacote jest-mock-extended sem se preocupar com as dependências externas (ex: Repositórios concretos)

Exemplo

Aqui temos uma interface que um repositório deve seguir.

import { UserDTO } from "../dtos/user";

export interface GetUsers {
    allUsers(): Promise<UserDTO[]>
}

Enter fullscreen mode Exit fullscreen mode

E no nosso serviço vamos ter uma injeção de dependência seguindo essa interface (sem dizer quem é a classe concreta que representa isso, Inversão de Dependência).

import { UserDTO } from "../dtos/user";
import { ServiceProtocol } from "../protocols/service";
import { GetUsers } from "../repos/get-users";

export default class GetAllUsersService implements ServiceProtocol<null, Promise<UserDTO[]>> {
    constructor (private readonly usersRepository: GetUsers) {}

    async perform(): Promise<UserDTO[]> {
        const all = await this.usersRepository.allUsers()
        return all
    }
}
Enter fullscreen mode Exit fullscreen mode

Agora nos testes, a mágica =)

Repare que na variável usersRepository estamos utilizando o MockProxy na interface e não na classe concreta e entre os testes estamos falando para que a mesma seja mockada.

Com isso, não se preocupamos com classe concreta de repositório, apenas com a camada do serviço que precisa ser testada.

import GetAllUsersService from "@/domain/services/get-all-users-service"
import { mock, MockProxy } from 'jest-mock-extended'
import { GetUsers } from "./repos/get-users"

describe('GetAllUsersService', () => {
    let sut: GetAllUsersService
    let usersRepository: MockProxy<GetUsers>

    beforeAll(() => {
       usersRepository = mock()
    })

    beforeEach(() => {
        sut = new GetAllUsersService(usersRepository)
        usersRepository.allUsers.mockResolvedValue([
            {
                name: 'user 1',
                email: 'user 1',
                age: 'user 1',
                departament: 'user 1',
                created_at: new Date(),
                inative: false
            },
        ])
    })

    it('should call allUsers one time', async () => {
        await sut.perform()

        expect(usersRepository.allUsers).toHaveBeenCalledTimes(1)
    })
})
Enter fullscreen mode Exit fullscreen mode

Valeu!

Referências: Manguinho

CÓDIGO-FONTE: https://github.com/Gabriel-Valin/clean-arch-express-based

Speedy emails, satisfied customers

Postmark Image

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (5)

Collapse
 
joaocrulhas profile image
João Pedro Rubira Crulhas

Gabriel, posso estar errado, mas acho que talvez colocar o mock no beforeEach() irá ser o mock para todos os testes.
Acho que a declaração do mockResolvedValue() faria mais sentido dentro do próprio teste em si.
Imagina a situação que vc queira testar
"Should return an empty array...."
Mas parabéns pelo POST :).

Collapse
 
gvt3ch profile image
Gabriel Valin • Edited

o mock no beforeEach eh usado para testar o metodo quando ele obtem sucesso, pode ser colocado no beforeAll tambem, ate melhor.

no caso para o teste de falha, nos colocariamos o mockReturnValueOnce, que sobrescreve o retorno do metodo inicialmente!

nesse teste ficaria method.mockReturnValueOnce([]) e continuamos com o teste =)))

Valeu !!

Collapse
 
joaocrulhas profile image
João Pedro Rubira Crulhas

Ah entendi, é que tenho usado mais o sinon para implementar meus testes.
No sinon se você tentar gerar um stub/mock de um módulo no forEach, e tentar realizar esse mesmo stub/mock, dentro de um teste, dará problemas.
Mas como você está utlizando o jest isso passa a ser possível.

Collapse
 
rodolphonetto profile image
Rodolpho Netto

Entendi o lance do mock porém como sou iniciante com testes... O que de fato está sendo testado? É a questão de saber se a service está recebendo a dependencia injetada e chamando o metodo correto?

Collapse
 
gvt3ch profile image
Gabriel Valin

Aqui a gente tem um teste simples, que testamos quantas vezes o método foi chamado. Garantimos a quantidade de execuções.

Mas com esse setup, podemos testar qualquer coisa... Forjando resultados, Rejeitando promises, etc...

A ideia é mais para que o setup seja construído rapidamente sem preocupações com a implantação de classes concretas (sendo spies, stubs ou de produção)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs