Реагировать модульным тестом со spyOn на useState не вызывает mocked-функцию, как следует - PullRequest
0 голосов
/ 10 апреля 2020

Я изучаю тестирование в React, у меня есть компонент с вводом, чтобы написать, что искать, поле выбора, чтобы выбрать, где искать, и кнопка для упорядочения по тому, что выбрано в поле выбора.

Я сделал тест, чтобы проверить, если я изменил опцию выбора, чтобы увидеть, вызвал ли он функцию setFilterBy. Он входит в функцию и изменяется, как и предсказывалось, от «title» до «author», но не обнаруживает вызов проверяемой функции. Кто-нибудь знает, что с этим не так?

Компонент

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

1 Ответ

0 голосов
/ 10 апреля 2020

Вы не должны проверять, используется ли 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');
});

Так и должно быть до конца теста. Если вы позже измените реализацию, чтобы установить свойство value в select, но сохраните его функциональность (скажем, например, вы измените свой компонент и определите его как компонент класса и используете метод 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. Это приводит к тому, что при вызове фиктивной версии поведение по умолчанию (обновление состояния и повторная визуализация компонента) не выполняется.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...