如何使用 Jest/Enzyme 在 React 中测试文件类型输入的更改处理程序?
How can I test a change handler for a file-type input in React using Jest/Enzyme?
我想测试我的 React 组件是否可以使用 FileReader
从 <input type="file"/>
元素导入用户选择的文件的内容。我下面的代码显示了一个测试失败的工作组件。
在我的测试中,我尝试使用 blob 代替文件,因为 blob 也可以是 "read" by FileReader
。这是一种有效的方法吗?我还怀疑部分问题是 reader.onload
是异步的,我的测试需要考虑到这一点。我需要在某处做出承诺吗?或者,我是否需要使用 jest.fn()
来模拟 FileReader
?
我真的更愿意只使用标准的 React 堆栈。特别是我想使用 Jest 和 Enzyme 而不必使用 Jasmine 或 Sinon 等。但是,如果你知道某些事情 can't 用 Jest/Enzyme 完成但是可以用另一种方式完成,这可能也会有所帮助。
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 访问代码的所有不同部分。但是,这并不一定意味着 应该 以这种方式测试所有这些部分。
code-under-test 与问题中的基本相同,只是我用 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;
我相信我已经成功地测试了 code-under-test 中的几乎所有内容(除了在评论中指出并在下面进一步讨论的一个例外)以下内容:
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(); });
// WARNING: But read the comment by Drenai for a potentially serious
// problem with the above test of `addEventListener`.
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
是否被调用。这看起来应该很容易,但无论出于何种原因,它仍然让我望而却步。它显然 has 被调用,因为其他模拟函数 within 它被确认已被调用但我还不能检查它是否本身被调用,使用 jest.fn()
甚至 Jasmine 的 spyOn
。我已在 SO 上询问 以尝试解决剩余的问题。
我想测试我的 React 组件是否可以使用 FileReader
从 <input type="file"/>
元素导入用户选择的文件的内容。我下面的代码显示了一个测试失败的工作组件。
在我的测试中,我尝试使用 blob 代替文件,因为 blob 也可以是 "read" by FileReader
。这是一种有效的方法吗?我还怀疑部分问题是 reader.onload
是异步的,我的测试需要考虑到这一点。我需要在某处做出承诺吗?或者,我是否需要使用 jest.fn()
来模拟 FileReader
?
我真的更愿意只使用标准的 React 堆栈。特别是我想使用 Jest 和 Enzyme 而不必使用 Jasmine 或 Sinon 等。但是,如果你知道某些事情 can't 用 Jest/Enzyme 完成但是可以用另一种方式完成,这可能也会有所帮助。
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 访问代码的所有不同部分。但是,这并不一定意味着 应该 以这种方式测试所有这些部分。
code-under-test 与问题中的基本相同,只是我用 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;
我相信我已经成功地测试了 code-under-test 中的几乎所有内容(除了在评论中指出并在下面进一步讨论的一个例外)以下内容:
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(); });
// WARNING: But read the comment by Drenai for a potentially serious
// problem with the above test of `addEventListener`.
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
是否被调用。这看起来应该很容易,但无论出于何种原因,它仍然让我望而却步。它显然 has 被调用,因为其他模拟函数 within 它被确认已被调用但我还不能检查它是否本身被调用,使用 jest.fn()
甚至 Jasmine 的 spyOn
。我已在 SO 上询问