Executando verificação de segurança...
11
thdr
6 min de leitura ·

Antes do TDD, por que você precisa saber o que são mocks, stubs e spies?

Olá, pessoal! Hoje trago um tema que acredito ser bastante interessante. Sei que existem dezenas de artigos na internet abordando TDD, BDD, design patterns para testes, como escrever testes, entre outros assuntos relacionados. No entanto, vejo poucos conteúdos explicando termos mais básicos dentro do universo de testes, aquelas funções que usamos frequentemente, mas nem sempre entendemos completamente o que significam ou como se comportam. Se você está começando a aprender sobre testes e não sabe exatamente o que as funções das bibliotecas fazem, este artigo é para você. Aproveite a leitura!

O que são mocks?

A primeira coisa que você pode se deparar assim que começar a escrever testes, são os mocks, as vezes você já usa, mas não sabe exatamente o que significa, então vamos lá.

Os mocks são utilizados principalmente em testes de unidade. Eles são ferramentas usadas para simular conteúdos, objetos, respostas que normalmente são vindas de uma dependência externa ou você precisa que o conteúdo tenha certa informação.

Imagine que você tá testando um sistema que recomenda filmes, esse sistema busca a lista de filmes numa API e retorna pra você.

O problema é: se toda vez que você rodar os testes a API real for chamada, isso pode ser lento e inconsistente (os filmes podem variar ou a API pode estar fora do ar), dificultando a confiabilidade dos testes.

Beleza Leo, entendi o problema, mas o que o mock resolve nesse caso? Bem, é simples, ao invés de você chamar a API, você vai ter a resposta dela como uma lista de filmes estáticas. É praticamente "fingir" que a API responde com aquela lista de filmes.

No exemplo do sistema de filmes, se você quer testar uma função chamada fetchMoviesFromAPI(), que utiliza a API para obter filmes, você pode criar um mock que simula a resposta da API assim:

// Isso é o mock
const MOVIES_FROM_API = [
	{
		id: 1,
		name: "Interstellar"
	},
	{
		id: 2,
		name: "Nosferatu"
	}
]

// Aqui estamos falando que a função fetchMoviesFromAPI vai devolver nosso mock ao invés de chamar a API real, isso é um stub, que você vai aprender no próximo tópico.
const fetchMoviesFromAPI = jest.fn().mockResolvedValue(MOVIES_FROM_API)

;(async () => {
	{
		const expectedMovies = MOVIES_FROM_API
		const movies = await fetchMoviesFromAPI()

		expect(movies).toEqual(MOVIES_FROM_API)
	}
})

Com mocks, seus testes se tornam mais eficientes, já que não precisam depender de serviços externos. Além disso, eles ganham em confiabilidade, pois você tem total controle sobre os retornos, permitindo que o foco permaneça na validação da funcionalidade, sem preocupações com possíveis instabilidades ou indisponibilidades da API.

Os mocks são objetos estáticos que simulam respostas de chamadas ou outros objetos necessários para o teste.

No fim, é como testar um carro sem usar gasolina de verdade. Você cria um ambiente controlado para garantir que o motor vai estar funcionando antes de sair com ele na estrada.

Entendi Mocks, agora o que são Stubs?

Stubs também são ferramentas de teste, mas têm uma função um pouco diferente. Eles substituem o comportamento de funções por algo pré-determinado, muitas vezes usando mocks para retornar valores específicos.

Os stubs substituem o comportamento da função. Por exemplo, quando eu acessar aquela API de filmes, a função não vai fazer a chamada real, e sim olhar pra nosso mock (a lista de filmes estáticas).

Eles também servem para nos lembrar que, nossos testes não devem depender de serviços externos ou a internet.

Então deixa eu te dar um pouco de contexto, imagina que você está testando um aplicativo que calcula o valor total de uma compra online. O cálculo inclui as taxas que são buscadas em um serviço externo. Toda vez que você roda o teste, esse cálculo precisar ser feito, consequentemente, o serviço externo precisaria ser chamado, o que poderia resultar em um teste lento, instável, custoso (porque o serviço externo pode cobrar a cada requisição) e inconsistente (os valores podem mudar).

Usando um stub, você vai substituir a chamada desse serviço real para retornar um valor fixo e pré-definido (sim, um mock). Em vez de chamar o serviço das taxas, você diz: "Sempre me devolva o valor 10 como se fosse a taxa".

Então imagina que você quer testar a função calcularTotalCompra(), que soma os valores dos itens do carrinho e adiciona a taxa de envio. Usando stubs, você substitui o serviço da taxa para um valor que sempre vai retornar "10" como taxa de envio. Desta forma:

// Função que queremos testar
async function calcularTotalCompra(itens, obterTaxaDeEnvio) {
	const taxaDeEnvio = await obterTaxaDeEnvio()
	const totalItens = itens.reduce((soma, item) => soma + item.preco, 0)
	return totalItens + taxaDeEnvio
}

// Stub para substituir o serviço de envio real
const obterTaxaDeEnvio = jest.fn().mockResolvedValue(10)

;(async () => {
	// Itens no carrinho
	const carrinho = [
		{ id: 1, nome: "Item 1", preco: 50 },
		{ id: 2, nome: "Item 2", preco: 30 }
	]

	// Calculando o total com o stub
	const total = await calcularTotalCompra(carrinho, obterTaxaDeEnvio)

	// Testando o resultado
	expect(total).toBe(90) // 50 + 30 + 10
})()

Isso simplifica o teste e garante sua reprodutibilidade, ou seja, ele sempre funcionará da mesma maneira. Além disso, os stubs ajudam a isolar o teste, eliminando a necessidade de se preocupar com o estado ou a disponibilidade da API de taxas.

Resumindo, é como se ao testar uma receita de bolo, você usa um medidor que sempre diz 200ml de leite ao invés de medir a quantidade real de leite, porque você só quer testar se consegue misturar os ingredientes, sem se preocupar se o leite está sendo medido certo ou não.

Mocks, stubs... e, finalmente, o que são spies?

Já exploramos os mocks, que simulam objetos, e os stubs, que imitam comportamentos de funções. Agora, vamos falar sobre os spies: o que exatamente eles fazem?

Spies monitoram funções, registrando quantas vezes foram chamadas, quais parâmetros receberam e os resultados de cada execução. Eles permitem observar o comportamento da função sem alterá-lo, garantindo que tudo esteja funcionando conforme o esperado.

Imagina ai que você ta testando o módulo de notificações do seu projeto, toda vez que um pedido é concluído, o sistema deve enviar uma mensagem para o cliente e registrar um log. Nesse caso, você só quer se certificar se essas ações estão sendo realizadas, mas não quer substituir nenhuma delas. Com um spy, você vai olhar para essas funções sem alterar o comportamento que elas realizam e com isso, você consegue ver:

  • Se a função foi chamada
  • Quantas vezes foi chamada
  • Quais argumentos ela recebeu.

Por exemplo, você quer testar a função finalizarPedido() que envia a notificação pro cliente e registra o log, com o spy, você verifica:

  • Se a função de notificação foi chamada
  • Se a função de registro de log foi chamada
  • Quais argumentos essas funções receberam.
// Função que queremos testar
async function finalizarPedido(cliente, enviarNotificacao, registrarLog) {
	await enviarNotificacao(cliente)
	registrarLog(`Pedido finalizado para o cliente: ${cliente.nome}`)
}

// Mocks das funções auxiliares
const enviarNotificacao = jest.fn()
const registrarLog = jest.fn()

;(async () => {
	// Cliente fictício
	const cliente = { id: 1, nome: "John Doe" }

	// Chamando a função que queremos testar
	await finalizarPedido(cliente, enviarNotificacao, registrarLog)

	// Verificando se as funções auxiliares foram chamadas
	expect(enviarNotificacao).toHaveBeenCalled()
	expect(enviarNotificacao).toHaveBeenCalledWith(cliente)

	expect(registrarLog).toHaveBeenCalled()
	expect(registrarLog).toHaveBeenCalledWith("Pedido finalizado para o cliente: John Doe")
})()

Para finalizar, é como colocar uma câmera para observar o que um cozinheiro faz em uma cozinha. Você não atrapalha o que ele está fazendo, apenas verifica se ele está seguindo a receita corretamente.

Então é isso, você aprendeu e entendeu os termos mocks, stubs e spies, que são os elementos fundamentais para criar testes confiáveis e eficiente. Agora você pode continuar aprofundando seus estudos, te vejo lá, até mais.

Carregando publicação patrocinada...
3

Muito bom!

Uma sugestão: nos códigos, para ter hilight usa ```javascript, por exemplo e aí vai ficar legal para uma melhor leitura do código.

Ex:

console.log('Hello, World')
1

Obrigado pelas explicações, e como você falou, muitas vezes já vimos ou já usamos, mas não entendemos o que está de fato está acontecendo e por que exatamente estamos usando cada coisa. Acho importante sabermos o que está acontecendo "debaixo do capô".Isso nós da mais confiança e segurança pra fazer o que precisamos fazer.