测试 setState 匿名函数调用
Testing setState anonymous function call
考虑 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]
还有一个类似按钮的实例增加索引,所以我想测试一下这两个按钮的行为是否正确。
您是在调用 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)
是否有任何实际意义。由于这个原因,将 setter 直接传递给另一个组件通常是一种代码味道。
耦合度较低的结构,其中 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();
});
});
(注意我遵循 priority guide 使用 user-facing,尽可能使用可访问的选择器。)
或者,如果 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
的模块中,并且 not 将其导出以供单独访问 - 处理按钮具有的事实作为 实施细节 .
提取到单独的组件
考虑 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]
还有一个类似按钮的实例增加索引,所以我想测试一下这两个按钮的行为是否正确。
您是在调用 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)
是否有任何实际意义。由于这个原因,将 setter 直接传递给另一个组件通常是一种代码味道。
耦合度较低的结构,其中 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();
});
});
(注意我遵循 priority guide 使用 user-facing,尽可能使用可访问的选择器。)
或者,如果 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
的模块中,并且 not 将其导出以供单独访问 - 处理按钮具有的事实作为 实施细节 .