web-dev-qa-db-ja.com

Jest / Enzymeを使用して、Reactのファイルタイプ入力の変更ハンドラーをテストするにはどうすればよいですか?

ReactコンポーネントがFileReaderを使用して_<input type="file"/>_要素からユーザーが選択したファイルの内容をインポートできるかどうかをテストしたい。テストが失敗したコンポーネント。

私のテストでは、ブロブもFileReaderによって「読み取る」ことができるため、ファイルの代わりにブロブを使用しようとしています。それは有効なアプローチですか?また、問題の一部は_reader.onload_が非同期であり、テストではこれを考慮する必要があると思われます。どこかに約束が必要ですか?あるいは、jest.fn()を使用してFileReaderをモックする必要がありますか?

私は本当に標準のReactスタックのみを使用することを望みます。特に、JestとEnzymeを使用し、JasmineやSinonなどを使用する必要はありません。 ca n'tはJest/Enzymeで実行できますが、canは別の方法で実行できます。これも役立つ場合があります。

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

この回答は、jestを使用してコードのさまざまな部分すべてにアクセスするhowを示しています。ただし、1つshouldでこれらすべての部分をこのようにテストする必要があるわけではありません。

テスト対象のコードは基本的に質問と同じですが、_addEventListener('load', ..._を_onload = ..._に置き換え、_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;
_

テスト中のコードのすべてをテストできたと思います(コメントに記載されている1つの例外を除き、以下でさらに説明します)。

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
});
_

まだ明示的にテストしていないことの1つは、changeHandlerが呼び出されたかどうかです。これは簡単なように思えますが、何らかの理由でそれはまだ私を避けています。明らかに呼び出されています、他のモックされた関数で呼び出されていることが確認されていますが、まだ確認できていませんjest.fn()またはJasmineのspyOnを使用して、それ自体が呼び出されました。 この他の質問 on SOこの残りの問題に対処するために尋ねました。

18
Andrew Willems