如何使用 Jest/Enzyme 在 React 中测试文件类型输入的更改处理程序?

Posted

技术标签:

【中文标题】如何使用 Jest/Enzyme 在 React 中测试文件类型输入的更改处理程序?【英文标题】:How can I test a change handler for a file-type input in React using Jest/Enzyme? 【发布时间】:2017-06-01 20:34:14 【问题描述】:

我想测试我的 React 组件是否可以使用 FileReader<input type="file"/> 元素导入用户选择的文件的内容。下面的代码显示了一个测试失败的工作组件。

在我的测试中,我尝试使用 blob 作为文件的替代品,因为 FileReader 也可以“读取” blob。这是一种有效的方法吗?我还怀疑部分问题是reader.onload 是异步的,我的测试需要考虑到这一点。我需要在某个地方做出承诺吗?或者,我是否可能需要使用jest.fn() 模拟FileReader

我真的更喜欢只使用标准的 React 堆栈。特别是我想使用 Jest 和 Enzyme 而不必使用,比如 Jasmine 或 Sinon 等。但是,如果你知道 不能 用 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');
);

【问题讨论】:

This discussion 似乎暗示使用 addEventListener 绕过了 React 处理事件的策略,因此例如酶并不真正支持。 我在第一条评论中提到addEventListener 的原因是因为其他网站建议addEventListener 可能比onload 更可测试。 (链接?)如果我理解正确,that discussion mentioned in my first comment 建议了一些我还没有开始工作的其他测试策略,但它指出这些可能的解决方案超出了 React/enzyme 的常规使用范围。但是,它似乎确实帮助至少一个人在使用 addEventListener 但没有提供很多细节的组件上测试 mousemove 事件。 【参考方案1】:

这个答案显示了如何使用 jest 访问代码的所有不同部分。然而,这并不一定意味着一个人应该以这种方式测试所有这些部分。

除了我将onload = ... 替换为addEventListener('load', ... 之外,被测代码与问题中的代码基本相同,并且我删除了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;

我相信我已经成功地测试了被测代码中的几乎所有内容(除了 cmets 中提到的一个例外,并在下面进一步讨论):

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 是否被调用。这似乎应该很容易,但无论出于何种原因,它仍然在逃避我。它显然 被调用,因为其他模拟函数 within 已确认已被调用,但我还无法检查它本身是否被调用,要么使用jest.fn() 甚至是 Jasmine 的 spyOn。我已经在 SO 上要求 this other question 尝试解决这个剩余的问题。

【讨论】:

spyOn(component, 'changeHandler').and.callThrough() 不起作用,因为您没有包装器的实例。如果您使用spyOn(componentWrapper.Instance(), 'changeHandler'),它将指向反应组件方法的当前实例。另一种方法是使用spyOn(MyComponent.prototype, 'changeHandler')。后者将放在mount 之后 您好,我需要这样的测试,但只是为了检查 FileReader 是否已被调用,但我收到错误 expect(jest.fn()).toHaveBeenCalled() Expected mock function to have been called. 任何帮助? pastiebin.com/5cef7ae41e03d 这是我的测试代码... 非常感谢,这是我所缺少的,您的样本帮助了我,.simulate('change', target: files: [file]); 这种测试方法的一个主要缺点是调用addEventListener 会在到达readAsText 代码之前立即调用处理程序。我们错过了代码的异步特性,并以从未发生过的顺序测试逻辑

以上是关于如何使用 Jest/Enzyme 在 React 中测试文件类型输入的更改处理程序?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Jest - Enzyme 测试 React 中 mapStateToProps 中的方法

如何使用 Jest/Enzyme 在功能性 React 组件中测试 lambda 函数?

使用 Jest / Enzyme 在 React 中的功能组件内部测试方法

如何使用 jest+enzyme 在 react-native 中选择一个元素(文本、视图)(主要是当文本/视图嵌套在组件的深处时)?

Jest/Enzyme 使用 try/catch 中的 async/await 方法测试 React 组件

React / Enzyme:运行 Jest / Enzyme 测试时出现 Invariant Violation 错误