web-dev-qa-db-fra.com

Mock Es6 classes utilisant Jest

J'essaie de simuler une classe ES6 avec un constructeur qui reçoit des paramètres, puis de simuler différentes fonctions de classe sur la classe pour continuer les tests, en utilisant Jest.

Le problème est que je ne trouve aucun document sur la façon d'aborder ce problème. J'ai déjà vu cet article , mais cela ne résout pas mon problème, car l'OP n'a en fait même pas besoin de se moquer de la classe! L'autre réponse de cet article n'est pas non plus élaborée, ne pointe vers aucune documentation en ligne et ne conduira pas à des connaissances reproductibles, car il s'agit simplement d'un bloc de code.

Alors disons que j'ai la classe suivante:

//socket.js;

module.exports = class Socket extends EventEmitter {

    constructor(id, password) {
        super();

        this.id = id;
        this.password = password;

        this.state = constants.socket.INITIALIZING;
    }

    connect() {
        // Well this connects and so on...
    } 

};

//__tests__/socket.js

jest.mock('./../socket');
const Socket = require('./../socket');
const socket = new Socket(1, 'password');

expect(Socket).toHaveBeenCalledTimes(1);

socket.connect()
expect(Socket.mock.calls[0][1]).toBe(1);
expect(Socket.mock.calls[0][2]).toBe('password');

Comme évident, la façon dont j'essaie de me moquer du socket et de la fonction de classe connect c'est faux, mais je ne trouve pas la bonne façon de le faire.

Veuillez expliquer, dans votre réponse, les étapes logiques que vous faites pour vous moquer de cela et pourquoi chacune d'elles est nécessaire + fournir des liens externes vers les documents officiels de Jest si possible!

Merci pour l'aide!

19
SpiXel

Mise à jour:

Toutes ces informations et plus ont maintenant été ajoutées aux documents Jest dans un nouveau guide, " ES6 Class Mocks ."

Divulgation complète: je l'ai écrit. : -)


La clé pour se moquer des classes ES6 est de savoir que une classe ES6 est une fonction . Par conséquent, la maquette doit également être une fonction.

  1. Appelez jest.mock('./mocked-class.js');, et importez également './mocked-class.js'.
  2. Pour toutes les méthodes de classe auxquelles vous souhaitez suivre les appels, créez une variable qui pointe vers une fonction factice, comme ceci: const mockedMethod = jest.fn();. Utilisez-les à l'étape suivante.
  3. Appelez MockedClass.mockImplementation(). Passez une fonction de flèche qui renvoie un objet contenant toutes les méthodes simulées, chaque ensemble à sa propre fonction de simulation (créée à l'étape 2).
  4. La même chose peut être faite en utilisant des maquettes manuelles (dossier __mocks__) pour se moquer des classes ES6. Dans ce cas, la maquette exportée est créée en appelant jest.fn().mockImplementation(), avec le même argument décrit dans (3) ci-dessus. Cela crée une fonction factice. Dans ce cas, vous devrez également exporter toutes les méthodes simulées que vous souhaitez espionner.
  5. La même chose peut être faite en appelant jest.mock('mocked-class.js', factoryFunction), où factoryFunction est à nouveau le même argument passé en 3 et 4 ci-dessus.

Un exemple vaut mille mots, alors voici le code. En outre, il y a un dépôt illustrant tout cela, ici: https://github.com/jonathan-stone/jest-es6-classes-demo/tree/mocks-working

Tout d'abord, pour votre code

si vous deviez ajouter le code d'installation suivant, vos tests devraient réussir:

const connectMock = jest.fn(); // Lets you check if `connect()` was called, if you want

Socket.mockImplementation(() => {
    return {
      connect: connectMock
    };
  });

(Remarque, dans votre code: Socket.mock.calls[0][1] devrait être [0][0], et [0][2] devrait être [0][1]. )

Ensuite, un exemple artificiel

avec quelques explications en ligne.

mocked-class.js . Remarque, ce code n'est jamais appelé pendant le test.

export default class MockedClass {
  constructor() {
    console.log('Constructed');
  }

  mockedMethod() {
    console.log('Called mockedMethod');
  }
}

mocked-class-consumer.js . Cette classe crée un objet à l'aide de la classe simulée. Nous voulons qu'il crée une version simulée au lieu de la vraie chose.

import MockedClass from './mocked-class';

export default class MockedClassConsumer {
  constructor() {
    this.mockedClassInstance = new MockedClass('yo');
    this.mockedClassInstance.mockedMethod('bro');
  }
}

mocked-class-consumer.test.js - le test:

import MockedClassConsumer from './mocked-class-consumer';
import MockedClass from './mocked-class';

jest.mock('./mocked-class'); // Mocks the function that creates the class; replaces it with a function that returns undefined.

// console.log(MockedClass()); // logs 'undefined'

let mockedClassConsumer;
const mockedMethodImpl = jest.fn();

beforeAll(() => {
  MockedClass.mockImplementation(() => {
    // Replace the class-creation method with this mock version.
    return {
      mockedMethod: mockedMethodImpl // Populate the method with a reference to a mock created with jest.fn().
    };
  });
});

beforeEach(() => {
  MockedClass.mockClear();
  mockedMethodImpl.mockClear();
});

it('The MockedClassConsumer instance can be created', () => {
  const mockedClassConsumer = new MockedClassConsumer();
  // console.log(MockedClass()); // logs a jest-created object with a mockedMethod: property, because the mockImplementation has been set now.
  expect(mockedClassConsumer).toBeTruthy();
});

it('We can check if the consumer called the class constructor', () => {
  expect(MockedClass).not.toHaveBeenCalled(); // Ensure our mockClear() is clearing out previous calls to the constructor
  const mockedClassConsumer = new MockedClassConsumer();
  expect(MockedClass).toHaveBeenCalled(); // Constructor has been called
  expect(MockedClass.mock.calls[0][0]).toEqual('yo'); // ... with the string 'yo'
});

it('We can check if the consumer called a method on the class instance', () => {
  const mockedClassConsumer = new MockedClassConsumer();
  expect(mockedMethodImpl).toHaveBeenCalledWith('bro'); 
// Checking for method call using the stored reference to the mock function
// It would be Nice if there were a way to do this directly from MockedClass.mock
});
21
stone