web-dev-qa-db-fra.com

Jest: mocking console.error - les tests échouent

Le problème:

J'ai un simple composant React que j'utilise pour apprendre à tester les composants avec Jest et Enzyme. Comme je travaille avec des accessoires, j'ai ajouté le module prop-types pour vérifier les propriétés en développement. prop-types utilise console.error pour alerter lorsque les accessoires obligatoires ne sont pas transmis ou lorsque les accessoires sont du type de données incorrect.

Je voulais me moquer de console.error pour compter le nombre de fois où il a été appelé par prop-types lorsqu’il est passé dans des accessoires manquants ou mal typés.

En utilisant cet exemple de composant et test simplifié, je m'attendrais à ce que les deux tests se comportent comme tels:

  1. Le premier test avec 0/2 accessoires requis devrait attraper l'appel simulé deux fois.
  2. Le deuxième test avec 1/2 accessoire requis devrait attraper la maquette appelée une fois.

Au lieu de cela, je reçois ceci:

  1. Le premier test s'exécute avec succès.
  2. Le deuxième test échoue, se plaignant que la fonction fictive a été appelée zéro fois.
  3. Si j'échange l'ordre des tests, le premier fonctionne et le second échoue.
  4. Si je scinde chaque test dans un fichier individuel, les deux fonctionnent.
  5. La sortie de console.error est supprimée, il est donc clair qu'elle est simulée pour les deux.

Je suis sûr qu'il me manque quelque chose d'évident, comme effacer le faux semblant ou autre.

Lorsque j'utilise la même structure par rapport à un module qui exporte une fonction, en appelant console.error un nombre de fois arbitraire, tout fonctionne. 

C'est quand je teste avec enzyme/react que je frappe ce mur après le premier test.

Exemple App.js:

import React, { Component } from 'react';
import PropTypes from 'prop-types';

export default class App extends Component {

  render(){
    return(
      <div>Hello world.</div>
    );
  }
};

App.propTypes = {
  id : PropTypes.string.isRequired,
  data : PropTypes.object.isRequired
};

Exemple App.test.js

import React from 'react';
import { mount } from 'enzyme';
import App from './App';

console.error = jest.fn();

beforeEach(() => {
  console.error.mockClear();
});

it('component logs two errors when no props are passed', () => {
  const wrapper = mount(<App />);
  expect(console.error).toHaveBeenCalledTimes(2);
});

it('component logs one error when only id is passed', () => {
  const wrapper = mount(<App id="stringofstuff"/>);
  expect(console.error).toHaveBeenCalledTimes(1);
});

Note finale: Ouais, il est préférable d'écrire le composant pour générer une sortie conviviale lorsque les accessoires sont manquants, puis effectuez un test. Mais une fois que j'ai trouvé ce comportement, je voulais comprendre ce que je faisais mal pour améliorer ma compréhension. Clairement, il me manque quelque chose.

5
Matthew Bakaitis

Tu n'as rien manqué. Il existe un problème connu ( https://github.com/facebook/react/issues/7047 ) concernant les messages d'erreur/d'avertissement manquants. 

Si vous modifiez vos scénarios de test ('... lorsque seul l'identifiant est passé' - le premier ',' ... lorsque aucun accessoire n'est transmis '- le second) et ajoutez un tel console.log('mockedError', console.error.mock.calls); dans vos scénarios de test , vous pouvez voir que le message sur l’identifiant manquant n’est pas déclenché lors du deuxième test. 

2
DLyman

Compte tenu du comportement expliqué par @DLyman, vous pouvez le faire comme ça:

describe('desc', () => {
    let spy = spyConsole();

    it('x', () => {
        // [...]
    });

    it('y', () => {
        // [...]
    });

    it('throws [...]', () => {
        shallow(<App />);
        expect(console.error).toHaveBeenCalled();
        expect(spy.console.mock.calls[0][0]).toContain('The prop `id` is marked as required');
    });
});

function spyConsole() {
    // https://github.com/facebook/react/issues/7047
    let spy = {};

    beforeAll(() => {
        spy.console = jest.spyOn(console, 'error').mockImplementation(() => {});
    });

    afterAll(() => {
        spy.console.mockRestore();
    });

    return spy;
}

J'ai rencontré un problème similaire, j'avais juste besoin de mettre en cache la méthode d'origine

const original = console.error

beforeEach(() => {
  console.error = jest.fn()
  console.error('you cant see me')
})

afterEach(() => {
  console.log('log still works')
  console.error('you cant see me')
  console.error.mockClear()
  console.error = original
  console.error('now you can')
})
2
lfender6445

Ce que les gars ont écrit ci-dessus est correct. J'ai rencontré un problème similaire et voici ma solution. Il prend également en considération la situation lorsque vous faites une affirmation sur l'objet simulé:

beforeAll(() => {
    // Create a spy on console (console.log in this case) and provide some mocked implementation
    // In mocking global objects it's usually better than simple `jest.fn()`
    // because you can `unmock` it in clean way doing `mockRestore` 
    jest.spyOn(console, 'log').mockImplementation(() => {});
  });
afterAll(() => {
    // Restore mock after all tests are done, so it won't affect other test suites
    console.log.mockRestore();
  });
afterEach(() => {
    // Clear mock (all calls etc) after each test. 
    // It's needed when you're using console somewhere in the tests so you have clean mock each time
    console.log.mockClear();
  });
1
Papi