Saltar al contenido principal
Versión: Siguiente

Mocks manuales

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 →

Los mocks manuales se utilizan para sustituir funcionalidad con datos simulados. Por ejemplo, en lugar de acceder a recursos remotos como un sitio web o una base de datos, puedes crear un mock manual que permita usar datos falsos. Esto garantiza que tus pruebas sean rápidas y no sean inestables.

Mockear módulos de usuario

Los mocks manuales se definen escribiendo un módulo en un subdirectorio __mocks__/ adyacente al módulo original. Por ejemplo, para mockear un módulo llamado user en el directorio models, crea un archivo user.js dentro de models/__mocks__.

precaución

El directorio __mocks__ distingue mayúsculas/minúsculas, así que nombrarlo como __MOCKS__ causará errores en algunos sistemas.

nota

Cuando requerimos ese módulo en nuestras pruebas (es decir, queremos usar el mock manual en lugar de la implementación real), es obligatorio llamar explícitamente a jest.mock('./moduleName').

Mockear módulos de Node

Si el módulo a mockear es de Node (ej: lodash), el mock debe ubicarse en el directorio __mocks__ al mismo nivel que node_modules (a menos que hayas configurado roots para apuntar a otra carpeta) y se mockeará automáticamente. No es necesario llamar explícitamente a jest.mock('module_name').

Los módulos con scope (también llamados scoped packages) pueden mockearse creando un archivo en una estructura de directorios que coincida con el nombre del módulo. Por ejemplo, para mockear @scope/project-name, crea un archivo en __mocks__/@scope/project-name.js, generando el directorio @scope/ correspondiente.

precaución

Para mockear módulos integrados de Node (ej: fs o path), es necesario llamar explícitamente a jest.mock('path') porque estos módulos no se mockean por defecto.

Ejemplos

.
├── config
├── __mocks__
│   └── fs.js
├── models
│   ├── __mocks__
│   │   └── user.js
│   └── user.js
├── node_modules
└── views

Cuando existe un mock manual para un módulo, Jest lo usará al llamar explícitamente a jest.mock('moduleName'). Sin embargo, cuando automock está en true, se usará el mock manual incluso sin llamar a jest.mock('moduleName'). Para desactivar este comportamiento, llama explícitamente a jest.unmock('moduleName') en pruebas que necesiten la implementación real.

información

Para que el mocking funcione correctamente, jest.mock('moduleName') debe estar en el mismo ámbito que la sentencia require/import.

Este es un ejemplo donde un módulo genera un resumen de archivos en un directorio usando el módulo nativo fs:

FileSummarizer.js
'use strict';

const fs = require('fs');

function summarizeFilesInDirectorySync(directory) {
return fs.readdirSync(directory).map(fileName => ({
directory,
fileName,
}));
}

exports.summarizeFilesInDirectorySync = summarizeFilesInDirectorySync;

Para evitar acceder al disco (lento y frágil), creamos un mock manual de fs extendiendo un mock automático. Implementaremos versiones personalizadas de las APIs de fs:

__mocks__/fs.js
'use strict';

const path = require('path');

const fs = jest.createMockFromModule('fs');

// This is a custom function that our tests can use during setup to specify
// what the files on the "mock" filesystem should look like when any of the
// `fs` APIs are used.
let mockFiles = Object.create(null);
function __setMockFiles(newMockFiles) {
mockFiles = Object.create(null);
for (const file in newMockFiles) {
const dir = path.dirname(file);

if (!mockFiles[dir]) {
mockFiles[dir] = [];
}
mockFiles[dir].push(path.basename(file));
}
}

// A custom version of `readdirSync` that reads from the special mocked out
// file list set via __setMockFiles
function readdirSync(directoryPath) {
return mockFiles[directoryPath] || [];
}

fs.__setMockFiles = __setMockFiles;
fs.readdirSync = readdirSync;

module.exports = fs;

Al escribir la prueba, debemos llamar explícitamente a jest.mock('fs') porque fs es un módulo nativo de Node:

__tests__/FileSummarizer-test.js
'use strict';

jest.mock('fs');

describe('listFilesInDirectorySync', () => {
const MOCK_FILE_INFO = {
'/path/to/file1.js': 'console.log("file1 contents");',
'/path/to/file2.txt': 'file2 contents',
};

beforeEach(() => {
// Set up some mocked out file info before each test
require('fs').__setMockFiles(MOCK_FILE_INFO);
});

test('includes all files in the directory in the summary', () => {
const FileSummarizer = require('../FileSummarizer');
const fileSummary =
FileSummarizer.summarizeFilesInDirectorySync('/path/to');

expect(fileSummary.length).toBe(2);
});
});

Este ejemplo usa jest.createMockFromModule para generar un mock automático y modificar su comportamiento. Este es el enfoque recomendado, pero es opcional. Si prefieres no usar el mock automático, exporta tus propias funciones desde el archivo mock. La desventaja de los mocks completamente manuales es que requieren actualizaciones manuales cuando cambia el módulo original. Por eso es mejor usar o extender el mock automático cuando sea posible.

Para garantizar que un simulacro manual y su implementación real permanezcan sincronizados, puede ser útil importar el módulo real usando jest.requireActual(moduleName) en tu simulacro manual y modificarlo con funciones simuladas antes de exportarlo.

El código de este ejemplo está disponible en examples/manual-mocks.

Uso con importaciones de módulos ES

Si usas importaciones de módulos ES, normalmente colocarás tus declaraciones import al inicio del archivo de pruebas. Pero frecuentemente necesitas indicar a Jest que use un simulacro antes de que los módulos lo utilicen. Por esta razón, Jest eleva automáticamente las llamadas jest.mock al inicio del módulo (antes de cualquier importación). Para aprender más sobre esto y verlo en acción, consulta este repositorio.

precaución

Las llamadas jest.mock no pueden elevarse al inicio del módulo si tienes habilitado el soporte para módulos ECMAScript. El cargador de módulos ESM siempre evalúa las importaciones estáticas antes de ejecutar el código. Consulta ECMAScriptModules para más detalles.

Simulación de métodos no implementados en JSDOM

Si algún código utiliza un método que JSDOM (la implementación DOM usada por Jest) aún no ha implementado, probarlo no es sencillo. Este es el caso, por ejemplo, de window.matchMedia(). Jest devuelve TypeError: window.matchMedia is not a function y no ejecuta correctamente la prueba.

En este caso, simular matchMedia en el archivo de pruebas debería resolver el problema:

Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // deprecated
removeListener: jest.fn(), // deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});

Esto funciona si window.matchMedia() se usa dentro de una función (o método) que se invoca durante la prueba. Si window.matchMedia() se ejecuta directamente en el archivo bajo prueba, Jest reportará el mismo error. En este caso, la solución es mover el simulacro manual a un archivo separado e incluirlo en la prueba antes del archivo bajo prueba:

import './matchMedia.mock'; // Must be imported before the tested file
import {myMethod} from './file-to-test';

describe('myMethod()', () => {
// Test the method here...
});