Skip to content

Simulaciones de Clase ES

Vitest se puede usar para burlarse de las clases de ES6 que se importan a los archivos que desea probar.

Las clases de ES6 son funciones constructoras con algo de azúcar sintáctica. Por lo tanto, cualquier simulación de una clase ES6 debe ser una función o una clase ES6 real (que es, de nuevo, otra función). Entonces puedes burlarte de ellos usando funciones simuladas.

Un Ejemplo de Clase ES6

Usaremos un ejemplo artificial de una clase que reproduce archivos de sonido, SoundPlayer, y una clase de consumidor que usa esa clase, SoundPlayerConsumer. Simularemos SoundPlayer en nuestras pruebas para SoundPlayerConsumer.

js
// src/sound-player.js
export default class SoundPlayer {
  constructor() {
    this.foo = 'bar';
  }

  playSoundFile(fileName) {
    console.log('Playing sound file ' + fileName);
  }
}
js
// src/sound-player-consumer.js
import SoundPlayer from '@/sound-player';

export default class SoundPlayerConsumer {  
  constructor() {
    this.soundPlayer = new SoundPlayer();
  }

  playSomethingCool() {
    const coolSoundFileName = 'song.mp3';
    this.soundPlayer.playSoundFile(coolSoundFileName);    
  }
}

Limpiando entre pruebas

Para borrar el registro de llamadas a la función constructora simulada y sus métodos, llamamos a mockClear() en la función beforeEach():

js
beforeEach(() => {
  SoundPlayer.mockClear();
  mockPlaySoundFile.mockClear();
});

Espiando métodos de nuestra clase

Nuestra clase simulada deberá proporcionar cualquier función miembro (playSoundFile en el ejemplo) que se llamará durante nuestras pruebas, o de lo contrario obtendremos un error por llamar a una función que no existe. Pero probablemente también querremos espiar las llamadas a esos métodos, para asegurarnos de que se hayan llamado con los parámetros esperados.

Se creará un nuevo objeto cada vez que se llame a la función constructora simulada durante las pruebas. Para espiar las llamadas a métodos en todos estos objetos, completamos playSoundFile con otra función simulada y almacenamos una referencia a esa misma función simulada en nuestro archivo de prueba, para que esté disponible durante las pruebas.

js
// tests/sound-player.spec.js
import SoundPlayer from '@/sound-player';
vi.mock('@/sound-player'); // SoundPlayer is now a mock constructor
const mockPlaySoundFile = vi.fn();
SoundPlayer = vi.fn().mockImplementation(() => {
  return {playSoundFile: mockPlaySoundFile}
});

El equivalente simulado manual de esto sería:

js
// src/__mocks__/sound-player.js
export const mockPlaySoundFile = vi.fn();
const mock = vi.fn().mockImplementation(() => {
  return {playSoundFile: mockPlaySoundFile};
});
export default mock;
// Import this named export into your test file

El uso es similar a la función de fábrica de módulos, excepto que quedará más limpia su prueba. Recuerde que debe importar el método simulado a su archivo de prueba, ya que ya no está definido allí. Use la ruta del módulo original para esto; no incluya __mocks__.

Ejemplo Completo

Aquí hay un ejemplo completo que usa el parámetro de fábrica del módulo para vi.mock:

js
// tests/sound-player-1.spec.js
import SoundPlayer from '@/sound-player';
import SoundPlayerConsumer from '@/sound-player-consumer';

vi.mock('@/sound-player'); // SoundPlayer is now a mock constructor
const mockPlaySoundFile = vi.fn();
SoundPlayer = vi.fn().mockImplementation(() => {
  return {playSoundFile: mockPlaySoundFile}
});

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.mock.calls[0][0]).toEqual(coolSoundFileName);
});

Finalmente, aquí hay otro ejemplo completo que usa la simulación manual para vi.mock:

js
// tests/sound-player-2.spec.js
import SoundPlayer, { mockPlaySoundFile } from '@/sound-player';
import SoundPlayerConsumer from '@/sound-player-consumer';
vi.mock('@/sound-player'); // SoundPlayer is now a mock constructor

beforeEach(() => {
  SoundPlayer.mockClear();
  mockPlaySoundFile.mockClear();
});

it('The consumer should be able to call new() on SoundPlayer', () => {
  const soundPlayerConsumer = new SoundPlayerConsumer();
  // Ensure constructor created the object:
  expect(soundPlayerConsumer).toBeTruthy();
});

it('We can check if the consumer called the class constructor', () => {
  // Show that mockClear() is working:
  expect(SoundPlayer).not.toHaveBeenCalled();
  
  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);
});