在useState上使用spyOn进行反应单元测试未按应调用模拟函数 [英] React unit test with spyOn on useState is not calling mocked function as it should

查看:84
本文介绍了在useState上使用spyOn进行反应单元测试未按应调用模拟函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在学习有关在React中进行测试的知识,我有一个组件来输入要搜索的内容,一个选择框来选择要搜索的位置以及一个按选择框中的内容进行排序的按钮.

I'm learning about testing in React, I have a component with input to write what to search, a select box to select where to search and a button to order by whats selected in the select box.

我进行了一项测试,以检查是否在更改选择框的选项时是否调用了setFilterBy函数.它进入函数内部,并按预期的方式从标题"更改为作者",但未检测到模拟函数的调用.有人知道这有什么问题吗?

I made a test to check if when I changed the option of select box, to see if it called the setFilterBy function. It goes inside the function and changes as predicted from "title" to "author" but it doesn't detect the call of the mocked function. Anyone knows whats wrong with this?

组件

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

推荐答案

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

You should not be testing if useState has been used within your component. Tests should ensure the behaviour of the component is the expected one, without taking into account its implementation details.

在这种情况下,您可以使用 useState 控制 select 元素的值,并且已经在测试用户选择新值后,其 value 属性会相应更改:

In this particular case, you use useState to control the value of the select element, and you are already testing that once the user selects a new value, its value property changes accordingly:

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 值),该测试将继续工作.

That should be it as far as the test goes. If you later change the implementation to set the value property in the select but maintain its functionality (say, for example, that you change your component and define it as a class component and use the setState method within the class to update the select value), the test will continue to work.

话虽如此,如果您仍然想检查是否正在使用 useState ,则必须使用

That being said, if you still want to check if useState is being used, you will have to mock the whole react module with jest.mock at the start of your test file:

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 中模拟返回的方法.这导致在调用模拟版本时,不会发生默认行为(更新状态并重新呈现组件).

Note that now you can't check if the value of your select element has changed, because you are mocking the returned method in useState. This causes that when the mock version is called, the default behaviour (updating the state and re-rendering the component) does not take place.

这篇关于在useState上使用spyOn进行反应单元测试未按应调用模拟函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆