【问题标题】:Testing an error thrown by a React component using testing-library and jest使用 testing-library 和 jest 由 React 组件抛出的测试错误
【发布时间】:2021-05-25 10:53:28
【问题描述】:

按照blog post 中解释的 Kent C Dodds 提供者模式,我有一个上下文提供者组件以及一个使用该上下文的钩子。

钩子防止在提供者之外使用它,

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.jsUserContext.test.js找到。

注意:可以在 Tests 选项卡下的代码框本身中运行测试。

【问题讨论】:

    标签: reactjs jestjs react-testing-library react-context


    【解决方案1】:

    正如你已经提到的,有expect().toThrow() :)

    所以在你的情况下:

      test("should throw error when not wrapped inside `UserProvider`", () => {
        expect(() => render(<TestComponent />))
          .toThrow("Cannot use `useUser` outside of `UserProvider`");
      });
    

    关于console.error: 目前没有办法关闭默认错误日志。 如果你想隐藏错误,你仍然需要模拟console.error

    当您模拟 console.error 之类的函数时,您希望在 afterEach 回调中恢复它们,以便在测试失败时也能恢复它们。

    【讨论】:

    • 这似乎不起作用。我在UserProvider]`之外得到Error: Uncaught [Error: Cannot use useUser`
    • 你是对的,测试确实通过了:) 仍然记录了一个错误,但没有被捕获。放入 try catch 是行不通的。难道没有办法在测试中捕捉到错误吗?
    • 我能够通过像这样模拟它来抑制 console.log 错误:const consoleErrorFn = jest.spyOn(console, 'error').mockImplementation(() =&gt; jest.fn()); 如果您确实走这条路,请不要忘记jest.restoreMocks()
    • 其实是 mockRestore:consoleErrorFn.mockRestore()
    【解决方案2】:

    你可以这样做

    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);
    });
    

    【讨论】:

    • 不确定component 是什么?
    【解决方案3】:

    它可能不是一个干净的解决方案,但它简单易懂。此示例使用 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");
    });
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-07-08
      • 1970-01-01
      • 2022-10-01
      • 2021-02-11
      • 2020-03-22
      • 2020-05-14
      • 2021-02-28
      • 2019-06-14
      相关资源
      最近更新 更多