In my login form, a successful login initiates a promise chain that ends with the user being redirected to the home screen. In the test below, I hope to ensure my login works by capturing that final step.
I have log statements within the code that tell me every step in the promise chain is executed as I expect, but the assertion still fails. It's clear from my logging that the test completes before the promise chain executes.
I think this might be complicated by the behavior of Formik, which I'm using in my actual form. I have not been able to successfully query and wait for the spinner that is displayed while the login is under way.
I'm at a loss as to how to get this test to wait until the navigation occurs. What promise resolution could be triggering waitFor to complete?
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"))
})
The login form:
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>
)
}