如何使用Jest / Enzyme在React中测试文件类型输入的更改处理程序?
我想测试我的React组件是否可以使用 FileReader
从< input type =中导入用户选择文件的内容file/>
元素。下面的代码显示了一个测试中断的工作组件。
I want to test whether my React component can use FileReader
to import the contents of a user-selected file from an <input type="file"/>
element. My code below shows a working component with a broken test.
在我的测试中,我试图使用blob作为文件的替代品,因为blob也可以阅读by FileReader
。这是一种有效的方法吗?我还怀疑问题的一部分是 reader.onload
是异步的,我的测试需要考虑到这一点。我需要某个地方的承诺吗?或者,我是否需要使用 jest.fn()
来模拟 FileReader
?
In my test I'm attempting to use a blob as a substitute for the file because blobs can also be "read" by FileReader
. Is that a valid approach? I also suspect that part of the issue is that reader.onload
is asynchronous and that my test needs to take this into consideration. Do I need a promise somewhere? Alternatively, do I perhaps need to mock FileReader
using jest.fn()
?
我真的更喜欢只使用标准的React堆栈。特别是我想使用Jest和Enzyme而不必使用,例如,Jasmine或Sinon等。但是如果你知道不能使用Jest / Enzyme但是可以以其他方式完成,这可能也会有所帮助。
I would really prefer to only use the standard React stack. In particular I want to use Jest and Enzyme and not have to use, say, Jasmine or Sinon, etc. However if you know something can't be done with Jest/Enzyme but can be done another way, that might also be helpful.
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');
});
此答案显示如何使用jest访问代码的所有不同部分。但是,这并不一定意味着应该以这种方式测试所有这些部分。
This answers shows how to access all of the different parts of the code using jest. However, it doesn't necessarily mean that one should test all of these parts this way.
代码 - 测试基本上与问题中的相同,除了我已经替换 addEventListener('load',...
for onload = ...
,我已删除 console.log
行:
The code-under-test is essentially the same as in the question except that I have substituted addEventListener('load', ...
for onload = ...
, and I have removed the console.log
line:
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;
我相信我已经设法测试了被测代码中的所有内容(使用在评论中注明并在下面进一步讨论的一个例外)具有以下内容:
I believe I've managed to test just about everything in the code-under-test (with the one exception noted in the comments and discussed further below) with the following:
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
});
我还没有明确测试的一件事是 changeHandler
被调用。这似乎应该很容易但无论出于何种原因,它仍然在逃避我。显然已经被调用了,因为中的其他模拟函数已被确认已被调用,但我还没有能够检查它本身是否被调用,或者使用 jest.fn()
甚至Jasmine的 spyOn
。我已经在SO上询问了这个其他问题,试图解决这个问题。
The one thing I haven't explicitly tested yet is whether or not changeHandler
was called. This seems like it should be easy but for whatever reason it is still eluding me. It clearly has been called, as other mocked functions within it are confirmed to have been called but I haven't yet been able to check whether it itself was called, either using jest.fn()
or even Jasmine's spyOn
. I have asked this other question on SO to try to address this remaining problem.