如何确保反应测试库测试等待完整的承诺链?

Stu*_*art 7 jestjs react-testing-library

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

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

我认为这可能会因为我在实际形式中使用的 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"))
})
Run Code Online (Sandbox Code Playgroud)

登录表单:

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>
  )
}
Run Code Online (Sandbox Code Playgroud)

Est*_*ask 6

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

waitFortoThrow其工作原理与错误处理类似。当断言被指定为参数、被调用一次、抛出错误并导致测试失败时,它不可能捕获expect错误并多次评估断言:

  await waitFor(expect(mockNav).toHaveBeenCalledWith("home"))
Run Code Online (Sandbox Code Playgroud)

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

  await waitFor(() => expect(mockNav).toHaveBeenCalledWith("home"))
Run Code Online (Sandbox Code Playgroud)

  • @TomDickman wait 是需要的,它不会影响 waitFor 的工作方式,但会影响其对测试的效果。基本上,测试不会等待 waitFor 应该满足的条件,这违背了目的。 (3认同)