Skip to main content
Versão: Próximo

Simulações Manuais

As simulações manuais (manual mocks) são usadas para substituir funcionalidade usando dados fictícios. Por exemplo, em vez de acessar um recurso remoto como um site ou um banco de dados, você pode optar por criar uma simulação manual que permite a usar dados falsos. Isso assegura que os seus testes serão rápidos, mais confiáveis e evitando inconsistências.

Mocking user modules

Simulações manuais são definidas a partir da criação de um módulo em um subdiretório __mocks__/ imediatamente adjacente ao módulo a ser simulado. Por exemplo, para simular um módulo chamado user no diretório models, crie um arquivo chamado user.js (ou user.ts ao usar TypeScript) e coloque ele no diretório models/__mocks__.

atenção

A pasta __mocks__ é sensível a maiúsculas e minúsculas, portanto, nomeá-la como __MOCKS__ pode causar problemas em alguns sistemas.

note

Quando precisamos utilizar esse módulo em nossos testes (o que significa que queremos usar a simulação manual em vez da implementação real), é necessário chamar, explicitamente, o jest.mock('./moduleName').

Simulando módulos Node

Se o módulo que você precisa simular é um módulo Node (ex.: lodash), o mock precisa ser colocado em um diretório __mocks__ adjacente ao diretório node_modules (a não ser que você tenha configurado o roots para apontar para um diretório diferente da raiz do projeto) e será simulado automaticamente. Não há necessidade de chamar explicitamente jest.mock('module_name').

Módulos com escopo (também conhecidos como pacotes com escopo) podem ser simulados criando um arquivo em uma estrutura de diretório que corresponde ao nome do módulo com escopo. Por exemplo, para simular um módulo chamado @escopo/project-name, crie um arquivo em __mocks__/@escopo/nome-do-projeto. s, criando o diretório @scope/ em conformidade.

atenção

Se desejarmos simular módulos internos do Node (por exemplo, fs ou path), então é necessário chamar explicitamente, por exemplo, jest.mock('path'), pois os módulos internos não são simulados por padrão.

Exemplos

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

Quando uma simulação manual existe para um determinado módulo, o sistema de módulo do Jest usará esse módulo ao chamar explicitamente jest.mock('moduleName'). However, when automock is set to true, the manual mock implementation will be used instead of the automatically created mock, even if jest.mock('moduleName') is not called. Para desativar esse comportamento, você precisará chamar explicitamente jest.unmock('moduleName') em testes que devem usar a implementação do efetiva do módulo.

informação

Para realizar a simulação adequadamente, o Jest necessita que jest.mock('moduleName') esteja no mesmo escopo que a instrução require/import.

Here's a contrived example where we have a module that provides a summary of all the files in a given directory. In this case, we use the core (built in) fs module.

FileSummarizer.js
'use strict';

const fs = require('fs');

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

exports.summarizeFilesInDirectorySync = summarizeFilesInDirectorySync;

Since we'd like our tests to avoid actually hitting the disk (that's pretty slow and fragile), we create a manual mock for the fs module by extending an automatic mock. Our manual mock will implement custom versions of the fs APIs that we can build on for our tests:

__mocks__/fs.js
'use strict';

const path = require('path');

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

// Esta é uma função personalizada que nossos testes podem usar durante a configuração para especificar
// como os arquivos no sistema de arquivos "mock" devem parecer quando qualquer uma das
// APIs `fs` for utilizada.
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));
}
}

// Uma versão personalizada de `readdirSync` que lê a partir da lista de arquivos simulados especial,
// configurada através de __setMockFiles.
function readdirSync(directoryPath) {
return mockFiles[directoryPath] || [];
}

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

module.exports = fs;

Now we write our test. Neste caso, jest.mock('fs')`\` deve ser chamado explicitamente, pois fs\ é um módulo interno do 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(() => {
// Configurar algumas informações de arquivo simuladas antes de cada teste.
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);
});
});

The example mock shown here uses jest.createMockFromModule to generate an automatic mock, and overrides its default behavior. This is the recommended approach, but is completely optional. If you do not want to use the automatic mock at all, you can export your own functions from the mock file. One downside to fully manual mocks is that they're manual – meaning you have to manually update them any time the module they are mocking changes. Because of this, it's best to use or extend the automatic mock when it works for your needs.

To ensure that a manual mock and its real implementation stay in sync, it might be useful to require the real module using jest.requireActual(moduleName) in your manual mock and amending it with mock functions before exporting it.

O código utilizado neste exemplo está disponível em: examples/manual-mocks.

Usando juntamente à importações de módulo ES

If you're using ES module imports then you'll normally be inclined to put your import statements at the top of the test file. But often you need to instruct Jest to use a mock before modules use it. For this reason, Jest will automatically hoist jest.mock calls to the top of the module (before any imports). To learn more about this and see it in action, see this repo.

atenção

jest.mock calls cannot be hoisted to the top of the module if you enabled ECMAScript modules support. The ESM module loader always evaluates the static imports before executing code. See ECMAScriptModules for details.

Simulando métodos que não são implementados no JSDOM

If some code uses a method which JSDOM (the DOM implementation used by Jest) hasn't implemented yet, testing it is not easily possible. This is e.g. the case with window.matchMedia(). Jest returns TypeError: window.matchMedia is not a function and doesn't properly execute the test.

In this case, mocking matchMedia in the test file should solve the issue:

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(),
})),
});

This works if window.matchMedia() is used in a function (or method) which is invoked in the test. If window.matchMedia() is executed directly in the tested file, Jest reports the same error. In this case, the solution is to move the manual mock into a separate file and include this one in the test before the tested file:

import './matchMedia.mock'; // Deve ser importando antes do arquivo a ser testado
import {myMethod} from './file-to-test';

describe('myMethod()', () => {
// Teste o método aqui...
});