Рассмотрим следующую кнопку в JSX:
interface Props {
index: number;
setIndex: Dispatch<SetStateAction<number>>;
}
export function Button({ index, setIndex }: Props) {
return (
<button
type = "button"
data-testid = "prev_btn"
onClick = {() => setIndex(prevIndex => prevIndex - 1)}
>
Previous
</button>
);
}
Я хотел бы написать тест в библиотеке тестирования Jest/React, который отображает кнопку и передает setIndex
в качестве реквизита, а затем проверяет, что setIndex
вызывается с правильным значением. Например:
const mockSetIndex = jest.fn();
const mockProps = {
setIndex: mockSetIndex,
index: 1,
};
describe('Button test', () => {
it('should update the index when the "previous" button is clicked', () => {
const { getByTestId } = render(<Button {...mockProps} />);
const prevBtn = getByTestId('prev_btn');
userEvent.click(prevBtn);
expect(mockSetIndex).toBeCalledWith(0);
});
});
Результат, который я получаю, - это сбой с:
expect(jest.fn()).toBeCalledWith(...expected)
Expected: 0
Received: [Function anonymous]
Есть еще один экземпляр аналогичной кнопки с индексом приращения, поэтому я хотел бы проверить правильность поведения обеих кнопок.
Он получает это из реквизита.
Хорошо, а как именно mockSetActiveSearchResultIndex
соединяется со всем остальным? Button
и ButtonForm
одно и то же? Вы должны предоставить пример, когда кто-то может зайти в ванильное приложение CRA --template typescript
и запустить, чтобы воссоздать проблему.
Но в основном вы звоните setIndex
с prevIndex => prevIndex - 1
, нет0
, поэтому непонятно, почему вы выбрали такое ожидание. Похоже, вы тестируете что-то, что управляется useState
в родительском элементе, и в этом случае он, вероятно, также должен быть проверено в этом контексте, а не издеваться.
Вы звоните setIndex
с функцией, а не с номером. Хотя это приемлемый аргумент в пользу setIndex
, это не то, что вы тестируете. Либо измените свою функцию на что-то вроде
interface Props {
index: number;
setIndex: Dispatch<SetStateAction<number>>;
}
export function Button({ index, setIndex }: Props) {
return (
<button
type = "button"
data-testid = "prev_btn"
onClick = {() => setIndex(index - 1)}
>
Previous
</button>
);
}
или проверьте, что он передает функцию с ожидаемым поведением
describe('Button test', () => {
it('should update the index when the "previous" button is clicked', () => {
const { getByTestId } = render(<Button {...mockProps} />);
const prevBtn = getByTestId('prev_btn');
userEvent.click(prevBtn);
const passedFunction = mockProps.setIndex.mock.calls[0][0]
expect(passedFunction(1)).toEqual(0);
});
});
Основываясь на свойствах Button
, я бы предположил, что его использование выглядит примерно так:
const Parent = () => {
const [index, setIndex] = useState(1);
return (
<>
<Button index = {index} setIndex = {setIndex} />
<p data-testid = "current_index">Current index: {index}</p>
</>
);
};
Это не дает Parent
контроля над собственным состоянием, Button
может вызвать, например, setIndex(100)
имеет ли это какое-то практическое значение. По этой причине передача сеттера непосредственно другому компоненту обычно является запахом кода.
Менее тесно связанная структура, где Parent
отвечает за свое состояние, будет выглядеть так:
const Parent = () => {
const [index, setIndex] = useState(1);
const decrement = () => setIndex((previousIndex) => previousIndex - 1);
return (
<>
<p>Current index: {index}</p>
<Button onClick = {decrement} />
</>
);
};
Это много легче тестировать:
describe("Button", () => {
it("should signal when the Previous button is clicked", () => {
const onClick = jest.fn();
render(<Button onClick = {onClick} />);
userEvent.click(screen.getByRole("button", { name: /previous/i }));
expect(onClick).toHaveBeenCalled();
});
});
(Обратите внимание, что я следую руководство по приоритетам, чтобы максимально использовать доступные пользователю селекторы.)
В качестве альтернативы, если Button
является Только, используемым в Parent
(как предполагает высокий уровень связи в состоянии), протестируйте его в этом контексте:
describe("Parent", () => {
it("should allow the previous thing to be selected", () => {
render(<Parent />);
userEvent.click(screen.getByRole("button", { name: /previous/i }));
// assert on actual outcome of index changing, e.g.
expect(screen.getByTestId("current_index")).toHaveTextContent("Current index: 0");
});
});
В этом случае тестирование на границе междуButton
и ее Parent
не совсем уместно. Чтобы указать на эту связь, вы можете переместить Button
в модуль, где Parent
определено, и нет экспортировать его для отдельного доступа — обработайте тот факт, что кнопка была извлечена в отдельный компонент как деталь реализации.
Получает ли тестируемый компонент setIndex из реквизита? Дайте минимальный воспроизводимый пример.