使用 testing-library 和 jest 测试 React 组件抛出的错误
Testing an error thrown by a React component using testing-library and jest
按照 Kent C Dodds 在此 blog post 中解释的提供程序模式,我有一个上下文提供程序组件以及一个使用该上下文的挂钩。
钩子防止在提供者之外使用它,
export function useUser() {
const { user } = useContext(UserContext) || {};
const { switchUser } = useContext(SwitchUserContext) || {};
if (!user || !switchUser) {
throw new Error('Cannot use `useUser` outside of `UserProvider`');
}
return { user, switchUser };
}
为了测试场景,我创建了一个 TestComponent
并在其中使用 useUser
挂钩。
function TestComponent() {
const { user, switchUser } = useUser();
return (
<>
<p>User: {user.name}</p>
<button onClick={switchUser}>Switch user</button>
</>
);
}
我是这样测试的,
test('should throw error when not wrapped inside `UserProvider`', () => {
const err = console.error;
console.error = jest.fn();
let actualErrorMsg;
try {
render(<TestComponent />);
} catch(e) {
actualErrorMsg = e.message;
}
const expectedErrorMsg = 'Cannot use `useUser` outside of `UserProvider`';
expect(actualErrorMsg).toEqual(expectedErrorMsg);
console.error = err;
});
我目前必须模拟 console.error
,然后在测试结束时将其设置为原始值。有用。但我想让它更具声明性和更简单。是否有一个很好的模式来实现它?
也许使用 .toThrow()?
我有一个codesandbox,上面的代码可以在UserContext.js
和UserContext.test.js
中找到。
注意:测试可以 运行 在 Tests
选项卡下的 codesandbox 中。
你可以这样做
test('should throw error when not wrapped inside `UserProvider`', () => {
component.useUser = jest.fn().mockRejectedValue(new Error('Cannot use `useUser` outside of `UserProvider`'));
let actualErrorMsg;
try {
render(<TestComponent />);
} catch(e) {
actualErrorMsg = e.message;
}
const expectedErrorMsg = 'Cannot use `useUser` outside of `UserProvider`';
expect(actualErrorMsg).toEqual(expectedErrorMsg);
});
正如您已经提到的 expect().toThrow()
:)
所以在你的情况下:
test("should throw error when not wrapped inside `UserProvider`", () => {
expect(() => render(<TestComponent />))
.toThrow("Cannot use `useUser` outside of `UserProvider`");
});
关于 console.error
:
目前没有办法关闭默认错误日志。
如果想隐藏错误,还是需要mock console.error
.
当您模拟像 console.error
这样的函数时,您希望在 afterEach
回调中恢复它们,以便在测试失败时也能恢复它们。
它可能不是一个干净的解决方案,但它简单易懂。此示例使用 TypeScript,但不使用它也能正常工作。像这样设置一次并在其他地方重复使用也相当容易。
it("errs if provider is missing", () => {
const HookWrapper = ({
testId,
}: {
testId?: string;
}) => {
try {
const data = useCustomHook();
return <pre data-testid={testId}>{JSON.stringify({ data }, null, 2)}</pre>;
} catch (err) {
const error = err as Error;
const errorPayload = { message: error.message, stack: error.stack };
return (
<pre data-testid={testId}>
{JSON.stringify({ error: errorPayload }, null, 2)}
</pre>
);
}
};
render(<HookWrapper testId="HookWrapper" />);
const providedData = JSON.parse(
screen.getByTestId("HookWrapper").innerHTML
);
const error = providedData.error as Error;
expect(error).toBeDefined();
expect(error.message).toEqual("SomeProvider Context not initialized");
});
按照 Kent C Dodds 在此 blog post 中解释的提供程序模式,我有一个上下文提供程序组件以及一个使用该上下文的挂钩。
钩子防止在提供者之外使用它,
export function useUser() {
const { user } = useContext(UserContext) || {};
const { switchUser } = useContext(SwitchUserContext) || {};
if (!user || !switchUser) {
throw new Error('Cannot use `useUser` outside of `UserProvider`');
}
return { user, switchUser };
}
为了测试场景,我创建了一个 TestComponent
并在其中使用 useUser
挂钩。
function TestComponent() {
const { user, switchUser } = useUser();
return (
<>
<p>User: {user.name}</p>
<button onClick={switchUser}>Switch user</button>
</>
);
}
我是这样测试的,
test('should throw error when not wrapped inside `UserProvider`', () => {
const err = console.error;
console.error = jest.fn();
let actualErrorMsg;
try {
render(<TestComponent />);
} catch(e) {
actualErrorMsg = e.message;
}
const expectedErrorMsg = 'Cannot use `useUser` outside of `UserProvider`';
expect(actualErrorMsg).toEqual(expectedErrorMsg);
console.error = err;
});
我目前必须模拟 console.error
,然后在测试结束时将其设置为原始值。有用。但我想让它更具声明性和更简单。是否有一个很好的模式来实现它?
也许使用 .toThrow()?
我有一个codesandbox,上面的代码可以在UserContext.js
和UserContext.test.js
中找到。
注意:测试可以 运行 在 Tests
选项卡下的 codesandbox 中。
你可以这样做
test('should throw error when not wrapped inside `UserProvider`', () => {
component.useUser = jest.fn().mockRejectedValue(new Error('Cannot use `useUser` outside of `UserProvider`'));
let actualErrorMsg;
try {
render(<TestComponent />);
} catch(e) {
actualErrorMsg = e.message;
}
const expectedErrorMsg = 'Cannot use `useUser` outside of `UserProvider`';
expect(actualErrorMsg).toEqual(expectedErrorMsg);
});
正如您已经提到的 expect().toThrow()
:)
所以在你的情况下:
test("should throw error when not wrapped inside `UserProvider`", () => {
expect(() => render(<TestComponent />))
.toThrow("Cannot use `useUser` outside of `UserProvider`");
});
关于 console.error
:
目前没有办法关闭默认错误日志。
如果想隐藏错误,还是需要mock console.error
.
当您模拟像 console.error
这样的函数时,您希望在 afterEach
回调中恢复它们,以便在测试失败时也能恢复它们。
它可能不是一个干净的解决方案,但它简单易懂。此示例使用 TypeScript,但不使用它也能正常工作。像这样设置一次并在其他地方重复使用也相当容易。
it("errs if provider is missing", () => {
const HookWrapper = ({
testId,
}: {
testId?: string;
}) => {
try {
const data = useCustomHook();
return <pre data-testid={testId}>{JSON.stringify({ data }, null, 2)}</pre>;
} catch (err) {
const error = err as Error;
const errorPayload = { message: error.message, stack: error.stack };
return (
<pre data-testid={testId}>
{JSON.stringify({ error: errorPayload }, null, 2)}
</pre>
);
}
};
render(<HookWrapper testId="HookWrapper" />);
const providedData = JSON.parse(
screen.getByTestId("HookWrapper").innerHTML
);
const error = providedData.error as Error;
expect(error).toBeDefined();
expect(error.message).toEqual("SomeProvider Context not initialized");
});