Saltar al contenido principal
Versión: 29.7

Funciones simuladas (mock)

Traducción Beta No Oficial

Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →

Las funciones simuladas (mock) te permiten probar las conexiones entre código eliminando la implementación real de una función, capturando las llamadas a la función (y los parámetros pasados en esas llamadas), capturando instancias de funciones constructoras cuando se instancian con new, y permitiendo configurar valores de retorno durante las pruebas.

Hay dos formas de simular funciones: creando una función mock para usar en el código de prueba, o escribiendo un manual mock para anular una dependencia de módulo.

Usando una función simulada

Imaginemos que estamos probando una implementación de la función forEach, que invoca un callback para cada elemento en un array proporcionado.

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

Para probar esta función, podemos usar una función simulada e inspeccionar su estado para verificar que el callback se invoca como se espera.

forEach.test.js
const forEach = require('./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);
});

Propiedad .mock

Todas las funciones simuladas tienen esta propiedad especial .mock, donde se almacenan datos sobre cómo se llamó la función y qué retornó. La propiedad .mock también rastrea el valor de this en cada llamada, permitiendo inspeccionarlo:

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> ]

Estos miembros mock son muy útiles en pruebas para verificar cómo se llaman, instancian o qué retornan estas funciones:

// 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

Las funciones mock también pueden inyectar valores de prueba en tu código durante una prueba:

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

Las funciones simuladas son muy efectivas en código que usa estilo de paso de continuación funcional. Este estilo evita necesidad de stubs complejos que recrean comportamiento de componentes reales, prefiriendo inyectar valores directamente en la prueba justo antes de usarse.

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

La mayoría de ejemplos reales implican obtener una función mock en un componente dependiente y configurarla, pero la técnica es igual. En estos casos, evita implementar lógica dentro de funciones que no se estén probando directamente.

Simulación de módulos

Supongamos una clase que obtiene usuarios de nuestra API. Usa axios para llamar a la API y retorna el atributo data que contiene todos los usuarios:

users.js
import axios from 'axios';

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

export default Users;

Para probar este método sin realmente llamar a la API (creando pruebas lentas y frágiles), podemos usar jest.mock(...) para simular automáticamente el módulo axios.

Al simular el módulo, podemos proporcionar un mockResolvedValue para .get que retorne datos para verificar. Esencialmente, hacemos que axios.get('/users.json') retorne una respuesta 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));
});

Simulación parcial

Subconjuntos de un módulo pueden simularse manteniendo el resto con su implementación 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');
});

Implementaciones simuladas

Aún hay casos donde es útil ir más allá de especificar valores de retorno y reemplazar completamente la implementación de una función mock. Esto se hace con jest.fn o el método mockImplementationOnce.

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

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

El método mockImplementation es útil cuando necesitas definir la implementación por defecto de una función mock creada desde otro 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

Cuando necesitas recrear comportamiento complejo donde múltiples llamadas producen resultados diferentes, usa 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

Cuando la función simulada agota las implementaciones definidas con mockImplementationOnce, ejecutará la implementación por defecto configurada con jest.fn (si está 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 métodos encadenados (que siempre deben retornar this), tenemos una API simplificada mediante la función .mockReturnThis() disponible en todos los mocks:

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

// is the same as

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

Nombres de simulaciones

Puedes asignar opcionalmente un nombre a tus funciones simuladas, que se mostrará en lugar de 'jest.fn()' en los resultados de errores de pruebas. Usa .mockName() si deseas identificar rápidamente qué función simulada está reportando un error en tus resultados.

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

Comparadores personalizados

Finalmente, para facilitar la verificación de cómo se han llamado las funciones simuladas, hemos añadido algunas funciones de comparación personalizadas:

// 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();

Estos comparadores son atajos para formas comunes de inspeccionar la propiedad .mock. Siempre puedes hacerlo manualmente si prefieres ese enfoque o necesitas algo más 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 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 una lista completa de comparadores, consulta la documentación de referencia.