Executando verificação de segurança...
2

[DÚVIDA] Qual a real utilidade de testes unitários mockados?

Tenho assistido o curso do Manguinho sobre clean architecture, e me surgiu uma dúvida no seguinte contexto:

Temos o seguinte adapter:

import { type EmailValidator } from '../protocols/email-validator'
import validator from 'validator'

export class EmailValidatorAdapter implements EmailValidator {
  isValid (email: string): boolean {
    return validator.isEmail(email)
  }
}

e os seguintes testes:

import { EmailValidatorAdapter } from './email-validator'
import validator from 'validator'

jest.mock('validator', () => ({
  isEmail (): boolean {
    return true
  }
}))

describe('Email Validator Adapter', () => {
  test('Should return false if validator returns false', () => {
    const sut = new EmailValidatorAdapter()
    jest.spyOn(validator, 'isEmail').mockReturnValueOnce(false)
    const isValid = sut.isValid('[email protected]')
    expect(isValid).toBe(false)
  })
  test('Should return true if validator returns true', () => {
    const sut = new EmailValidatorAdapter()
    const isValid = sut.isValid('[email protected]')
    expect(isValid).toBe(true)
  })
})

No curso ele diz que a única coisa que interessa no teste é o retorno, e é por isso que ele mocka o primeiro teste Should return false if validator returns false. Porém se vermos o email passado por parametro, esse é um email válido, tem toda a estrutura comum de um email, e o validator retorna true.

Tendo em vista isso, e se eu quiser mockar "ao contrário"? Adicionar um email de formato inválido no parametro e quiser mockar o retorno como true? Pq de fato não utilizamos o método isEmail do validator nos testes?

A um tempo atrás eu já vi discussões em torno da utilidade desses testes, mas no contexto de estudos que eu estava passando não dei muita atenção. Gostaria da ajuda dos senhores para entender melhor a real utilidade de um teste mockado dessa forma. Obrigado 😊

Carregando publicação patrocinada...
3

Nesse exemplo não precisava mockar o módulo "validator", era só usar injeção de dependência e pronto. Aí no teste era só passar um stub que responde ao método isEmail.

Mas respondendo a sua dúvida, mockar dependências em testes unitários te ajuda a isolar unidades do sistema. Por exemplo, se tiver alguma coisa errada no validador de e-mails, o teste que deveria falhar é o teste do validador, não quem depende dele. Isso faz com que os testes unitários sejam mais assertivos sobre o "subject" que está sendo exercitado.

Nada impede que no teste do EmailValidatorAdapter vc use um e-mail invalido e o mock retorne que o e-mail é valido, o objetivo é exercitar as responsabilidades do adapter, não da validação em si.

1
2

A ideia básica do teste unitário é testar uma parte específica do código de cada vez.

Basta pensar assim: "Eu tenho esse pedacinho de código aqui, que está no meio de um monte de outras coisas. Se todas essas outras coisas funcionarem, esse pedacinho também funciona?"

Mas como vc faz pra garantir que todas as outras coisas estão fazendo o que vc quer? Vc poderia testar acessando o sistema, até chegar na situação que quer testar. Só que aí vc estará testando toda a funcionalidade, então não é mais teste unitário.

Como vc só quer testar aquele pedacinho específico e nada mais, então vc simula as outras coisas. Usar mocks é uma das maneiras de fazer isso. Basicamente vc cria um mock pra cada coisa que não está sendo testada, e aí vc faz elas retornarem o que vc precisa.

No seu caso, vc quer testar o EmailValidatorAdapter. Basicamente, o método isValid deve retornar exatamente o mesmo valor que o validator retorna.

Ou seja, quero testar o comportamento do método isValid: se todo o resto funcionar, isValid também funciona? E o que é "o resto"? É o validator.

Eu não quero testar se o validator funciona (se ele de fato valida corretamente os emails). Isso seria parte de outro teste - por exemplo, um teste unitário só pro validator, em que vc passa vários emails e verifica se ele valida corretamente.

Não, aqui eu só quero testar o comportamento do EmailValidatorAdapter, mais especificamente do método isValid. Se o validator retorna true, isValid também retorna true? Se o validator retorna false, isValid também retorna false?

Então o que eu faço? Como o teste só está focando em isValid, e o validator faz parte do "resto" (do que não me interessa testar no momento), eu crio um mock do validator que retorna o que eu preciso em cada caso de teste.

Primeiro eu crio um mock que retorna false e verifico se isValid também vai retornar false. Depois faço o mesmo com true. E poderia fazer mais casos de teste, se tivessem mais valores possíveis.


Claro que este caso é muito simples, mas tem casos mais complexos em que mocks podem ser bem mais úteis. Por exemplo, se o código precisa acessar algum recurso externo (banco de dados, consultar uma API web, se conectar com outro serviço remoto, etc) e fazer algo com o resultado da consulta. Em vez de acessar estes recursos, vc cria mocks destes, que retornam o que vc precisa. Aí vc consegue testar a parte que manipula o resultado, de maneira isolada e independente (como um teste unitário deve ser).

"Ah, mas e se eu quiser testar o acesso ao recurso externo?"

Aí não é mais teste unitário. Nesse caso, seria mais um teste de integração.

1

Mockar é uma forma de simplificadar os testes, tornando eles independentes de módulos externos por exemplo.

Existem inumeros casos que ajuda, por exemplo se você tem um serviço que chama uma API web por exemplo.
Você não quer essa api sendo chamada a cada vez que roda o teste, pois essa chamada leva tempo (100ms que seja, se seu teste chamar a api 50x já são 5 segundos.) e/ou custa dinheiro.

Em geral, não se testa coisas externas ao seu código. Por exemplo, se seu teste precisa escrever no sistema de arquivos durante a execução, é provável que você esteja testando errado.

Imagine que você roda o teste de um codigo OK, mas a API está fora do ar, ou você está sem internet. Nessa situação seu teste irá falhar erroneamente, afinal o código está certo.