【问题标题】:React unit test with spyOn on useState is not calling mocked function as it should在 useState 上使用 spyOn 进行反应单元测试没有调用模拟函数,因为它应该
【发布时间】:2020-07-22 15:59:08
【问题描述】:

我正在学习如何在 React 中进行测试,我有一个用于编写搜索内容的输入组件、一个用于选择搜索位置的选择框以及一个用于按选择框中选择的内容排序的按钮。

我做了一个测试,检查当我改变选择框的选项时,看看它是否调用了 setFilterBy 函数。它进入函数内部并按照预测从“标题”更改为“作者”,但它没有检测到模拟函数的调用。有谁知道这是怎么回事?

组件

import React, { useState } from "react"
import PropTypes from "prop-types"

interface Props {
    handleFilterChange: (event: React.ChangeEvent<HTMLInputElement>, filterBy: string) => void
    handleSortClick: (filterBy: string) => void
    total: number
    filtered: number
}

export const PostFilter: React.FC<Props> = ({ handleFilterChange, handleSortClick, total, filtered }) => {
    const [filterBy, setFilterBy] = useState<string>("title")

    const handleSelect = (event: React.ChangeEvent<HTMLSelectElement>) => {
        console.log("a")
        setFilterBy(event.target.value)
    }

    return (
        <div>
            <label> Filter </label>
            <input className="search-input" type="text" name="search" onChange={event => handleFilterChange(event, filterBy)}/>
            <span style={{ padding: "4px" }}>{ filtered > 0 && `${total - filtered} posts found.` } </span>
            By
            <select className="search-type" style={{ margin: "4px" }} value={filterBy} onChange={handleSelect}>
                <option value="title">Title</option>
                <option value="author">Author</option>
            </select>
            <button className="sort-button" onClick={event => handleSortClick(filterBy)}> Sort </button>
        </div>
    )
}

测试

import React from "react"
import { mount } from "enzyme"
import { PostFilter } from "./PostFilter"

const handleFilterChange = jest.fn()
const handleSortClick = jest.fn()
const setState = jest.fn()

describe("PosFilter", () => {
    let wrapper

    beforeEach(() => {
        jest.spyOn(React, 'useState').mockImplementation(initState => [initState, setState]);

        wrapper = mount(
            <PostFilter handleFilterChange={handleFilterChange} handleSortClick={handleSortClick} total={10} filtered={4} /> 
        );
    });

    it("Changing select button should call setState", () => {
        const select = wrapper.find(".search-type")
        expect(select.instance().value).toBe("title")
        wrapper.find(".search-type").simulate('change', {
            target: {
                value: "author"
            }
        })
        expect(select.instance().value).toBe("author")
        expect(setState).toHaveBeenCalledTimes(1);
    })
});

结果:

console.log src/components/threads/PostFilter.tsx:15
      a
    expect(jest.fn()).toHaveBeenCalledTimes(expected)

    Expected number of calls: 1
    Received number of calls: 0

【问题讨论】:

    标签: javascript reactjs jestjs


    【解决方案1】:

    您不应该测试您的组件中是否使用了useState。测试应确保组件的行为符合预期,而不考虑其实现细节。

    在这种特殊情况下,您使用useState 来控制select 元素的值,并且您已经在测试一旦用户选择了一个新值,它的value 属性就会相应地发生变化:

    it("Changing select button updates its value", () => {
        const select = wrapper.find('.search-type');
        expect(select.prop('value')).toBe('title');
    
        select.simulate('change', {
            target: {
                value: 'author'
            }
        });
        expect(wrapper.find('.search-type').prop('value')).toBe('author');
    });
    

    就测试而言,应该就是这样。如果您稍后更改实现以在select 中设置value 属性但保持其功能(例如,您更改组件并将其定义为类组件并在类中使用setState 方法更新select 值),测试将继续进行。

    话虽如此,如果您仍想检查是否正在使用 useState,则必须在测试文件的开头使用 jest.mock 模拟整个反应模块:

    import React from 'react';
    import { mount } from 'enzyme';
    import { PostFilter } from '../PostFilter';
    
    const handleFilterChange = jest.fn();
    const handleSortClick = jest.fn();
    const setState = jest.fn();
    
    jest.mock('react', () => {
        const actualReact = jest.requireActual('react');
    
        return {
            ...actualReact,
            useState: jest.fn()
        };
    });
    
    describe('PosFilter', () => {
        let wrapper;
    
    
        beforeEach(() => {
            jest.spyOn(React, 'useState').mockImplementation(initState => [initState, setState]);
    
            wrapper = mount(
                <PostFilter
                    handleFilterChange={handleFilterChange}
                    handleSortClick={handleSortClick}
                    total={10}
                    filtered={4}
                /> 
            );
        });
    
        it('Changing select button should call setState', () => {
            const select = wrapper.find('.search-type');
            expect(select.prop('value')).toBe('title');
    
            select.simulate('change', {
                target: {
                    value: 'author'
                }
            });
            expect(setState).toHaveBeenCalledTimes(1);
            expect(setState).toHaveBeenCalledWith('author');
        });
    });
    

    请注意,现在您无法检查 select 元素的值是否已更改,因为您正在模拟 useState 中返回的方法。这导致当调用模拟版本时,默认行为(更新状态和重新渲染组件)不会发生。

    【讨论】:

    • 如果您使用jest.mock(),则不需要jest.spyOn(),因为您可以在useState: jest.fn(initState =&gt; [initState, setState])) 上添加实现。看来 OP 遇到的实际问题是 stackoverflow.com/questions/64165138/… 上讨论的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-03-29
    • 2020-03-19
    • 2020-06-26
    • 1970-01-01
    • 1970-01-01
    • 2020-06-09
    • 2017-01-29
    相关资源
    最近更新 更多