如何确保 react-testing-library 测试等待完成的承诺链?

How to ensure a react-testing-library test waits for a completed promise chain?

在我的登录表单中,成功登录会启动一个以用户被重定向到主屏幕结束的承诺链。在下面的测试中,我希望通过捕获最后一步来确保我的登录有效。

我在代码中有日志语句告诉我承诺链中的每一步都按我预期的那样执行,但断言仍然失败。从我的日志记录中可以清楚地看出,测试在承诺链执行之前完成。

我认为这可能会因 Formik 的行为而变得复杂,我正在以我的实际形式使用它。我无法成功查询并等待登录时显示的微调器。

我不知道如何让这个测试等到导航发生。什么承诺决议可能会触发 waitFor 完成?

import { act, render, screen, waitFor } from "@testing-library/react"
import userEvent from "@testing-library/user-event"

import React from "react"

import { AuthProvider } from "context/auth-context"
import { rest } from "msw"
import { setupServer } from "msw/node"
import { MemoryRouter as Router } from "react-router-dom"
import { LoginScreen } from "screens/login"
import { handlers } from "test/auth-handlers"
import { buildLoginForm } from "test/generate/auth"
import { deferred } from "test/test-utils"

const Wrapper = ({ children }) => (
  <Router>
    <AuthProvider>{children}</AuthProvider>
  </Router>
)
const serverURL = process.env.REACT_APP_SERVER_URL
const server = setupServer(...handlers)

const mockNav = jest.fn()
jest.mock("react-router-dom", () => ({
  ...jest.requireActual("react-router-dom"),
  useNavigate: () => mockNav,
}))

beforeAll(() => {
  server.listen()
})
afterAll(() => server.close())
afterEach(() => {
  server.resetHandlers()
  jest.clearAllMocks()
})

test("successful login", async () => {
  const { promise, resolve } = deferred()

  render(
    <Wrapper>
      <LoginScreen />
    </Wrapper>,
  )

  expect(screen.getByLabelText(/loading/i)).toBeInTheDocument()

  await act(() => {
    resolve()
    return promise
  })

  const { email, password } = buildLoginForm()

  userEvent.type(screen.getByRole("textbox", { name: /email/i }), email)
  userEvent.type(screen.getByLabelText(/password/i), password)
  userEvent.click(screen.getByRole("button"))

  await waitFor(expect(mockNav).toHaveBeenCalledWith("home"))
})

登录表单:

function LoginForm({ onSubmit }) {
  const { isError, isLoading, error, run } = useAsync()

  function handleSubmit(values) {
    // any 400 or 500 is displayed to the user
    run(onSubmit(values)).catch(() => {})
  }

  return (
    <Formik
      initialValues={{ email: "", password: "" }}
      validationSchema={Yup.object({
        email: Yup.string().email("Invalid email address").required("A valid email is required"),
        password: Yup.string().required("Password is required"),
      })}
      onSubmit={(values) => handleSubmit(values)}
    >
      <Form>
        <FormGroup name="email" type="text" label="Email" />
        <FormGroup name="password" type="password" label="Password" />
        <IconSubmitButton loading={isLoading} color="green">
          <MdArrowForward style={{ marginTop: ".6rem" }} />
        </IconSubmitButton>
        {isError ? <ErrorDisplay error={error} /> : null}
      </Form>
    </Formik>
  )
}

waitFor 不知道承诺或其他实现细节,它通过在指定的时间间隔内轮询提供的断言来工作,直到断言通过或发生超时。

waitFor 在错误处理方面与 类似。当它被指定为参数时,它无法捕获错误并多次评估断言,expect 被调用一次,抛出错误并未能通过测试:

  await waitFor(expect(mockNav).toHaveBeenCalledWith("home"))

waitFor 唯一可行的方法是为它提供一个可以在内部用 try..catch 包装并多次执行的函数。正确的做法是:

  await waitFor(() => expect(mockNav).toHaveBeenCalledWith("home"))