Simulação de Classes ES6
Jest pode ser usado para simular (mock, em inglês) classes ES6 que são importadas para arquivos que você deseja testar.
Classes ES6 são funções de construtor com uma sintaxe mais fácil de usar. Portanto, qualquer simulação de uma classe ES6 deve ser uma função ou uma classe ES6 real (que é, novamente, outra função). So you can mock them using mock functions.
Um Exemplo de Classe ES6
We'll use a contrived example of a class that plays sound files, SoundPlayer, and a consumer class which uses that class, SoundPlayerConsumer. We'll mock SoundPlayer in our tests for SoundPlayerConsumer.
export default class SoundPlayer {
constructor() {
this.foo = 'bar';
}
playSoundFile(fileName) {
console.log('Playing sound file ' + fileName);
}
}
import SoundPlayer from './sound-player';
export default class SoundPlayerConsumer {
constructor() {
this.soundPlayer = new SoundPlayer();
}
playSomethingCool() {
const coolSoundFileName = 'song.mp3';
this.soundPlayer.playSoundFile(coolSoundFileName);
}
}
As 4 maneiras de criar uma simulação de uma classe ES6
Simulação automática
Calling jest.mock('./sound-player') returns a useful "automatic mock" you can use to spy on calls to the class constructor and all of its methods. It replaces the ES6 class with a mock constructor, and replaces all of its methods with mock functions that always return undefined. Method calls are saved in theAutomaticMock.mock.instances[index].methodName.mock.calls.
If you use arrow functions in your classes, they will not be part of the mock. Isto ocorre porque as arrow functions não estão presentes no prototype do objeto, elas são apenas propriedades que contêm uma referência para uma função.
Se você não precisa substituir a implementação da classe, esta é a opção mais fácil de configurar. Por exemplo:
import SoundPlayer from './sound-player';
import SoundPlayerConsumer from './sound-player-consumer';
jest.mock('./sound-player'); // SoundPlayer is now a mock constructor
beforeEach(() => {
// Clear all instances and calls to constructor and all methods:
SoundPlayer.mockClear();
});
it('We can check if the consumer called the class constructor', () => {
const soundPlayerConsumer = new SoundPlayerConsumer();
expect(SoundPlayer).toHaveBeenCalledTimes(1);
});
it('We can check if the consumer called a method on the class instance', () => {
// Show that mockClear() is working:
expect(SoundPlayer).not.toHaveBeenCalled();
const soundPlayerConsumer = new SoundPlayerConsumer();
// Constructor should have been called again:
expect(SoundPlayer).toHaveBeenCalledTimes(1);
const coolSoundFileName = 'song.mp3';
soundPlayerConsumer.playSomethingCool();
// mock.instances is available with automatic mocks:
const mockSoundPlayerInstance = SoundPlayer.mock.instances[0];
const mockPlaySoundFile = mockSoundPlayerInstance.playSoundFile;
expect(mockPlaySoundFile.mock.calls[0][0]).toBe(coolSoundFileName);
// Equivalent to above check:
expect(mockPlaySoundFile).toHaveBeenCalledWith(coolSoundFileName);
expect(mockPlaySoundFile).toHaveBeenCalledTimes(1);
});
Simulação manual
Create a manual mock by saving a mock implementation in the __mocks__ folder. Isso permite que você especifique a implementação, e pode ser usada em arquivos de teste.
// Import this named export into your test file:
export const mockPlaySoundFile = jest.fn();
const mock = jest.fn().mockImplementation(() => {
return {playSoundFile: mockPlaySoundFile};
});
export default mock;
Importe a simulação e o método simulado compartilhado por todas as instâncias:
import SoundPlayer, {mockPlaySoundFile} from './sound-player';
import SoundPlayerConsumer from './sound-player-consumer';
jest.mock('./sound-player'); // SoundPlayer is now a mock constructor
beforeEach(() => {
// Clear all instances and calls to constructor and all methods:
SoundPlayer.mockClear();
mockPlaySoundFile.mockClear();
});
it('We can check if the consumer called the class constructor', () => {
const soundPlayerConsumer = new SoundPlayerConsumer();
expect(SoundPlayer).toHaveBeenCalledTimes(1);
});
it('We can check if the consumer called a method on the class instance', () => {
const soundPlayerConsumer = new SoundPlayerConsumer();
const coolSoundFileName = 'song.mp3';
soundPlayerConsumer.playSomethingCool();
expect(mockPlaySoundFile).toHaveBeenCalledWith(coolSoundFileName);
});
Calling jest.mock() with the module factory parameter
jest.mock(path, moduleFactory) takes a module factory argument. Um "module factory" é uma função que retorna uma simulação.
Para simular uma função de construtor, o "module factory" deve retornar uma função de construtor. Em outras palavras, o "module factory" deve ser uma função que retorna uma função - uma função de ordem superior, também chamada de "higher-order function" (HOF).
import SoundPlayer from './sound-player';
const mockPlaySoundFile = jest.fn();
jest.mock('./sound-player', () => {
return jest.fn().mockImplementation(() => {
return {playSoundFile: mockPlaySoundFile};
});
});
Since calls to jest.mock() are hoisted to the top of the file, Jest prevents access to out-of-scope variables. By default, you cannot first define a variable and then use it in the factory. Jest will disable this check for variables that start with the word mock. However, it is still up to you to guarantee that they will be initialized on time. Be aware of Temporal Dead Zone.
For example, the following will throw an out-of-scope error due to the use of fake instead of mock in the variable declaration.
// Note: this will fail
import SoundPlayer from './sound-player';
const fakePlaySoundFile = jest.fn();
jest.mock('./sound-player', () => {
return jest.fn().mockImplementation(() => {
return {playSoundFile: fakePlaySoundFile};
});
});
The following will throw a ReferenceError despite using mock in the variable declaration, as the mockSoundPlayer is not wrapped in an arrow function and thus accessed before initialization after hoisting.
import SoundPlayer from './sound-player';
const mockSoundPlayer = jest.fn().mockImplementation(() => {
return {playSoundFile: mockPlaySoundFile};
});
// results in a ReferenceError
jest.mock('./sound-player', () => {
return mockSoundPlayer;
});
Replacing the mock using mockImplementation() or mockImplementationOnce()
You can replace all of the above mocks in order to change the implementation, for a single test or all tests, by calling mockImplementation() on the existing mock.
Chamadas para jest.mock são "hoisted" para o topo do código. You can specify a mock later, e.g. in beforeAll(), by calling mockImplementation() (or mockImplementationOnce()) on the existing mock instead of using the factory parameter. Isso também permite que você altere a simulação entre os testes, se necessário:
import SoundPlayer from './sound-player';
import SoundPlayerConsumer from './sound-player-consumer';
jest.mock('./sound-player');
describe('When SoundPlayer throws an error', () => {
beforeAll(() => {
SoundPlayer.mockImplementation(() => {
return {
playSoundFile: () => {
throw new Error('Test error');
},
};
});
});
it('Should throw an error when calling playSomethingCool', () => {
const soundPlayerConsumer = new SoundPlayerConsumer();
expect(() => soundPlayerConsumer.playSomethingCool()).toThrow();
});
});
Em profundidade: Compreensão das funções do construtor das simulações (mock, em inglês)
Building your constructor function mock using jest.fn().mockImplementation() makes mocks appear more complicated than they really are. Esta seção mostra como você pode criar suas próprias simulações para ilustrar como funciona o simulador.
Simulação manual que é outra classe ES6
If you define an ES6 class using the same filename as the mocked class in the __mocks__ folder, it will serve as the mock. Esta classe será usada no lugar da classe verdadeira. Isso permite que você injete uma implementação de teste para a classe, mas não fornece uma maneira de espiar as chamadas.
Para o exemplo inventado, a simulação pode ser assim:
export default class SoundPlayer {
constructor() {
console.log('Mock SoundPlayer: constructor was called');
}
playSoundFile() {
console.log('Mock SoundPlayer: playSoundFile was called');
}
}
Simular (mock, em inglês) usando parâmetro de "module factory"
The module factory function passed to jest.mock(path, moduleFactory) can be a HOF that returns a function*. This will allow calling new on the mock. Novamente, isso permite que você injete um comportamento diferente para testes, mas não fornece uma maneira de espiar as chamadas.