Ir para o conteúdo principal
Versão: 30.0

Funções Mock (Simuladas)

Tradução Beta Não Oficial

Esta página foi traduzida por PageTurner AI (beta). Não é oficialmente endossada pelo projeto. Encontrou um erro? Reportar problema →

Funções mock (simuladas) permitem testar as conexões entre códigos ao substituir a implementação real de uma função, capturar chamadas à função (e os parâmetros passados nessas chamadas), capturar instâncias de funções construtoras quando instanciadas com new e permitir a configuração de valores de retorno durante o teste.

Existem duas maneiras de simular funções: criando uma função mock para usar no código de teste ou escrevendo um manual mock para substituir uma dependência de módulo.

Usando uma função mock

Vamos imaginar que estamos testando uma implementação da função forEach, que invoca um callback para cada item em um array fornecido.

forEach.js
export function forEach(items, callback) {
for (const item of items) {
callback(item);
}
}

Para testar essa função, podemos usar uma função mock e inspecionar o estado do mock para garantir que o callback é invocado conforme esperado.

forEach.test.js
import {forEach} from './forEach';

const mockCallback = jest.fn(x => 42 + x);

test('forEach mock function', () => {
forEach([0, 1], mockCallback);

// The mock function was called twice
expect(mockCallback.mock.calls).toHaveLength(2);

// The first argument of the first call to the function was 0
expect(mockCallback.mock.calls[0][0]).toBe(0);

// The first argument of the second call to the function was 1
expect(mockCallback.mock.calls[1][0]).toBe(1);

// The return value of the first call to the function was 42
expect(mockCallback.mock.results[0].value).toBe(42);
});

Propriedade .mock

Todas as funções mock possuem essa propriedade especial .mock, que armazena dados sobre como a função foi chamada e o que ela retornou. A propriedade .mock também rastreia o valor de this para cada chamada, então também é possível inspecionar isso:

const myMock1 = jest.fn();
const a = new myMock1();
console.log(myMock1.mock.instances);
// > [ <a> ]

const myMock2 = jest.fn();
const b = {};
const bound = myMock2.bind(b);
bound();
console.log(myMock2.mock.contexts);
// > [ <b> ]

Esses membros mock são muito úteis em testes para verificar como essas funções são chamadas, instanciadas ou o que retornaram:

// The function was called exactly once
expect(someMockFunction.mock.calls).toHaveLength(1);

// The first arg of the first call to the function was 'first arg'
expect(someMockFunction.mock.calls[0][0]).toBe('first arg');

// The second arg of the first call to the function was 'second arg'
expect(someMockFunction.mock.calls[0][1]).toBe('second arg');

// The return value of the first call to the function was 'return value'
expect(someMockFunction.mock.results[0].value).toBe('return value');

// The function was called with a certain `this` context: the `element` object.
expect(someMockFunction.mock.contexts[0]).toBe(element);

// This function was instantiated exactly twice
expect(someMockFunction.mock.instances.length).toBe(2);

// The object returned by the first instantiation of this function
// had a `name` property whose value was set to 'test'
expect(someMockFunction.mock.instances[0].name).toBe('test');

// The first argument of the last call to the function was 'test'
expect(someMockFunction.mock.lastCall[0]).toBe('test');

Valores de Retorno Simulados

Funções mock também podem ser usadas para injetar valores de teste no seu código durante um teste:

const myMock = jest.fn();
console.log(myMock());
// > undefined

myMock.mockReturnValueOnce(10).mockReturnValueOnce('x').mockReturnValue(true);

console.log(myMock(), myMock(), myMock(), myMock());
// > 10, 'x', true, true

Funções mock também são muito eficazes em códigos que usam um estilo funcional de passagem de continuação. Códigos escritos nesse estilo ajudam a evitar a necessidade de stubs complicados que recriam o comportamento do componente real que estão substituindo, preferindo injetar valores diretamente no teste logo antes de serem usados.

const filterTestFn = jest.fn();

// Make the mock return `true` for the first call,
// and `false` for the second call
filterTestFn.mockReturnValueOnce(true).mockReturnValueOnce(false);

const result = [11, 12].filter(num => filterTestFn(num));

console.log(result);
// > [11]
console.log(filterTestFn.mock.calls[0][0]); // 11
console.log(filterTestFn.mock.calls[1][0]); // 12

A maioria dos exemplos do mundo real envolve obter uma função mock em um componente dependente e configurá-la, mas a técnica é a mesma. Nesses casos, tente evitar a tentação de implementar lógica dentro de qualquer função que não está sendo testada diretamente.

Simulando Módulos

Suponha que temos uma classe que busca usuários da nossa API. A classe usa axios para chamar a API e retorna o atributo data que contém todos os usuários:

users.js
import axios from 'axios';

class Users {
static all() {
return axios.get('/users.json').then(resp => resp.data);
}
}

export default Users;

Agora, para testar esse método sem realmente acessar a API (e assim criar testes lentos e frágeis), podemos usar a função jest.mock(...) para simular automaticamente o módulo axios.

Uma vez que o módulo é simulado, podemos fornecer um mockResolvedValue para .get que retorna os dados contra os quais queremos que nosso teste faça asserções. Na prática, estamos dizendo que queremos que axios.get('/users.json') retorne uma resposta falsa.

users.test.js
import axios from 'axios';
import Users from './users';

jest.mock('axios');

test('should fetch users', () => {
const users = [{name: 'Bob'}];
const resp = {data: users};
axios.get.mockResolvedValue(resp);

// or you could use the following depending on your use case:
// axios.get.mockImplementation(() => Promise.resolve(resp))

return Users.all().then(data => expect(data).toEqual(users));
});

Simulando Partes

Subconjuntos de um módulo podem ser simulados e o restante do módulo pode manter sua implementação real:

foo-bar-baz.js
export const foo = 'foo';
export const bar = () => 'bar';
export default () => 'baz';
//test.js
import defaultExport, {bar, foo} from '../foo-bar-baz';

jest.mock('../foo-bar-baz', () => {
const originalModule = jest.requireActual('../foo-bar-baz');

//Mock the default export and named export 'foo'
return {
__esModule: true,
...originalModule,
default: jest.fn(() => 'mocked baz'),
foo: 'mocked foo',
};
});

test('should do a partial mock', () => {
const defaultExportResult = defaultExport();
expect(defaultExportResult).toBe('mocked baz');
expect(defaultExport).toHaveBeenCalled();

expect(foo).toBe('mocked foo');
expect(bar()).toBe('bar');
});

Implementações de Mock

Ainda assim, há casos em que é útil ir além da capacidade de especificar valores de retorno e substituir completamente a implementação de uma função mock. Isso pode ser feito com jest.fn ou o método mockImplementationOnce em funções mock.

const myMockFn = jest.fn(cb => cb(null, true));

myMockFn((err, val) => console.log(val));
// > true

O método mockImplementation é útil quando você precisa definir a implementação padrão de uma função mock que é criada a partir de outro módulo:

foo.js
module.exports = function () {
// some implementation;
};
test.js
jest.mock('../foo'); // this happens automatically with automocking
const foo = require('../foo');

// foo is a mock function
foo.mockImplementation(() => 42);
foo();
// > 42

Quando você precisa recriar um comportamento complexo de uma função mock, onde múltiplas chamadas de função produzem resultados diferentes, use o método mockImplementationOnce:

const myMockFn = jest
.fn()
.mockImplementationOnce(cb => cb(null, true))
.mockImplementationOnce(cb => cb(null, false));

myMockFn((err, val) => console.log(val));
// > true

myMockFn((err, val) => console.log(val));
// > false

Quando a função mock esgota as implementações definidas com mockImplementationOnce, ela executará a implementação padrão definida com jest.fn (se estiver definida):

const myMockFn = jest
.fn(() => 'default')
.mockImplementationOnce(() => 'first call')
.mockImplementationOnce(() => 'second call');

console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn());
// > 'first call', 'second call', 'default', 'default'

Para casos em que temos métodos que normalmente são encadeados (e, portanto, sempre precisam retornar this), temos uma API simplificada na forma de uma função .mockReturnThis() que também está disponível em todos os mocks:

const myObj = {
myMethod: jest.fn().mockReturnThis(),
};

// is the same as

const otherObj = {
myMethod: jest.fn(function () {
return this;
}),
};

Nomes de Mocks

Você pode opcionalmente fornecer um nome para suas funções mock, que será exibido em vez de 'jest.fn()' na saída de erro do teste. Use .mockName() se quiser identificar rapidamente a função mock que está reportando um erro na saída do seu teste.

const myMockFn = jest
.fn()
.mockReturnValue('default')
.mockImplementation(scalar => 42 + scalar)
.mockName('add42');

Matchers Personalizados

Por fim, para facilitar a verificação de como funções mock foram chamadas, adicionamos algumas funções matcher personalizadas para você:

// The mock function was called at least once
expect(mockFunc).toHaveBeenCalled();

// The mock function was called at least once with the specified args
expect(mockFunc).toHaveBeenCalledWith(arg1, arg2);

// The last call to the mock function was called with the specified args
expect(mockFunc).toHaveBeenLastCalledWith(arg1, arg2);

// All calls and the name of the mock is written as a snapshot
expect(mockFunc).toMatchSnapshot();

Esses matchers são atalhos para formas comuns de inspecionar a propriedade .mock. Você sempre pode fazer isso manualmente se preferir ou se precisar fazer algo mais específico:

// The mock function was called at least once
expect(mockFunc.mock.calls.length).toBeGreaterThan(0);

// The mock function was called at least once with the specified args
expect(mockFunc.mock.calls).toContainEqual([arg1, arg2]);

// The last call to the mock function was called with the specified args
expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1]).toEqual([
arg1,
arg2,
]);

// The first arg of the last call to the mock function was `42`
// (note that there is no sugar helper for this specific of an assertion)
expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1][0]).toBe(42);

// A snapshot will check that a mock was invoked the same number of times,
// in the same order, with the same arguments. It will also assert on the name.
expect(mockFunc.mock.calls).toEqual([[arg1, arg2]]);
expect(mockFunc.getMockName()).toBe('a mock name');

Para uma lista completa de matchers, consulte a documentação de referência.