web-dev-qa-db-fra.com

Comment puis-je tester un gestionnaire de modifications pour une entrée de type fichier dans React using Jest / Enzyme?

Je veux tester si mon composant React peut utiliser FileReader pour importer le contenu d'un fichier sélectionné par l'utilisateur à partir d'un élément <input type="file"/>. Mon code ci-dessous montre un composant de travail avec un test cassé.

Dans mon test, j'essaie d'utiliser un blob comme substitut du fichier car les blobs peuvent également être "lus" par FileReader. Est-ce une approche valable? Je soupçonne également qu'une partie du problème est que reader.onload Est asynchrone et que mon test doit en tenir compte. Ai-je besoin d'une promesse quelque part? Sinon, dois-je peut-être me moquer de FileReader en utilisant jest.fn()?

Je préférerais vraiment utiliser uniquement la pile standard React. En particulier, je veux utiliser Jest et Enzyme et ne pas avoir à utiliser, disons, Jasmine ou Sinon, etc. Cependant, si vous savez quelque chose ne peut pas être fait avec Jest/Enzyme mais peut être fait d'une autre manière , cela pourrait également être utile.

MyComponent.js:

import React from 'react';
class MyComponent extends React.Component {
    constructor(props) {
        super(props);
        this.state = {fileContents: ''};
        this.changeHandler = this.changeHandler.bind(this);
    }
    changeHandler(evt) {
        const reader = new FileReader();
        reader.onload = () => {
            this.setState({fileContents: reader.result});
            console.log('file contents:', this.state.fileContents);
        };
        reader.readAsText(evt.target.files[0]);
    }
    render() {
        return <input type="file" onChange={this.changeHandler}/>;
    }
}
export default MyComponent;

MyComponent.test.js:

import React from 'react'; import {shallow} from 'enzyme'; import MyComponent from './MyComponent';
it('should test handler', () => {
    const blob = new Blob(['foo'], {type : 'text/plain'});
    shallow(<MyComponent/>).find('input')
        .simulate('change', { target: { files: [ blob ] } });
    expect(this.state('fileContents')).toBe('foo');
});
18
Andrew Willems

Cette réponse montre comment pour accéder à toutes les différentes parties du code en utilisant jest. Cependant, cela ne signifie pas nécessairement que l'on devrait tester toutes ces parties de cette façon.

Le code en cours de test est essentiellement le même que dans la question, sauf que j'ai substitué addEventListener('load', ... À onload = ..., Et j'ai supprimé la ligne console.log:

MyComponent.js :

import React from 'react';
class MyComponent extends React.Component {
    constructor(props) {
        super(props);
        this.state = {fileContents: ''};
        this.changeHandler = this.changeHandler.bind(this);
    }
    changeHandler(evt) {
        const reader = new FileReader();
        reader.addEventListener('load', () => {
            this.setState({fileContents: reader.result});
        });
        reader.readAsText(evt.target.files[0]);
    }
    render() {
        return <input type="file" onChange={this.changeHandler}/>;
    }
}
export default MyComponent;

Je crois que j'ai réussi à tester à peu près tout dans le code sous test (à l'exception d'une exception notée dans les commentaires et discutée plus loin) avec les éléments suivants:

MyComponent.test.js :

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

it('should test handler', () => {
    const componentWrapper   = mount(<MyComponent/>);
    const component          = componentWrapper.get(0);
    // should the line above use `componentWrapper.instance()` instead?
    const fileContents       = 'file contents';
    const expectedFinalState = {fileContents: fileContents};
    const file               = new Blob([fileContents], {type : 'text/plain'});
    const readAsText         = jest.fn();
    const addEventListener   = jest.fn((_, evtHandler) => { evtHandler(); });
    const dummyFileReader    = {addEventListener, readAsText, result: fileContents};
    window.FileReader        = jest.fn(() => dummyFileReader);

    spyOn(component, 'setState').and.callThrough();
    // spyOn(component, 'changeHandler').and.callThrough(); // not yet working

    componentWrapper.find('input').simulate('change', {target: {files: [file]}});

    expect(FileReader        ).toHaveBeenCalled    (                             );
    expect(addEventListener  ).toHaveBeenCalledWith('load', jasmine.any(Function));
    expect(readAsText        ).toHaveBeenCalledWith(file                         );
    expect(component.setState).toHaveBeenCalledWith(expectedFinalState           );
    expect(component.state   ).toEqual             (expectedFinalState           );
    // expect(component.changeHandler).toHaveBeenCalled(); // not yet working
});

La seule chose que je n'ai pas encore explicitement testée est de savoir si changeHandler a été appelé ou non. Cela semble être facile mais pour quelque raison que ce soit, cela m'échappe encore. Il est clair que a a été appelé, comme d'autres fonctions moquées à l'intérieur il est confirmé qu'il a été appelé mais je n'ai pas encore pu vérifier si c'était lui-même appelé, en utilisant jest.fn() ou même spyOn de Jasmine. J'ai demandé cette autre question sur SO pour essayer de résoudre ce problème restant.

18
Andrew Willems