React testing-library is very convenient to test React components rendering from props, fire events and check DOM elements. react-router uses a <Redirect>
component to trigger a redirect, but how can we test that this component is called using testing-library?
React testing-library非常方便地测试从props,fire事件和检查DOM元素渲染的React组件。 react-router使用<Redirect>
组件触发重定向,但是如何使用testing-library测试该组件是否被调用?
Let’s say we have a CreateBookForm component that creates a new book. It calls our API when the form is submitted.
假设我们有一个CreateBookForm组件可以创建一本新书。 提交表单后,它将调用我们的API。
// BookCreateForm.js
import React, { useState } from 'react';
import api from './api';
function CreateBookForm() {
const [title, setTitle] = useState('');
async function handleSubmit(event) {
event.preventDefault();
await api.createBook({ title });
}
return (
<form onSubmit={handleSubmit}>
<input
placeholder="Book's title"
value={title}
onChange={(event) => setTitle(event.target.value)}
/>
<button>Create book</button>
</form>
);
}
export default CreateBookForm;
It’s easy to test that our api is called when the form is submitted with testing-library:
使用test-library提交表单时,很容易测试是否调用了我们的api:
// BookCreateForm.test.js
import React from 'react';
import { render, act, fireEvent, waitFor } from '@testing-library/react';
import BookCreateForm from './BookCreateForm';
import api from './api';
jest.mock('./api');
test('it calls api on form submit', async () => {
api.createBook = jest.fn(() => Promise.resolve({ id: 1 }));
const {
getByPlaceholderText, getByText, findByDisplayValue
} = render(<BookCreateForm />);
await act(async () => {
const input = getByPlaceholderText(/Book's title/);
fireEvent.change(input, { target: { value: 'Yama Loka Terminus' }});
await findByDisplayValue(/Yama Loka Terminus/);
const button = getByText(/Create book/);
fireEvent.click(button);
});
expect(api.createBook).toHaveBeenCalledWith({ title: 'Yama Loka Terminus' });
});
Now, let’s say we want our component to redirect to the new book page once it’s created.
现在,假设我们希望我们的组件在创建后重新定向到新书页面。
// BookCreateForm.js
import React, { useState } from 'react';
import { Redirect } from 'react-router-dom'
import api from './api';
function CreateBookForm() {
const [title, setTitle] = useState('');
const [createdId, setCreatedId] = useState(null);
async function handleSubmit(event) {
event.preventDefault();
const { id } = await api.createBook({ title });
setCreatedId(id);
}
return createdId ?
<Redirect to={`/book/${createdId}`}/> :
(
<form onSubmit={handleSubmit}>
<input
placeholder="Book's title"
value={title}
onChange={(event) => setTitle(event.target.value)}
/>
<button>Create book</button>
</form>
);
}
export default CreateBookForm;
We’ll probably have a router wrapping our form and a BookPage component:
我们可能会有一个路由器来包装我们的表单和一个BookPage组件:
// App.js
function App() {
return (
<div className="App">
<BrowserRouter>
<Route path="/book/create">
<BookCreateForm />
</Route>
<Route path="/book/:id">
<BookPage />
</Route>
</BrowserRouter>
</div>
);
}
Now, our test runner will complain that we use `<Redirect>` outside of a router, so let’s wrap our component test into one.
现在,我们的测试运行者会抱怨我们在路由器外部使用了<Redirect>,因此让我们将组件测试包装为一个。
// BookCreateForm.test.js
// …
import { BrowserRouter } from 'react-router-dom';
// …
const {
container,
getByPlaceholderText,
getByText,
findByDisplayValue
} = render(<BrowserRouter><BookCreateForm /></BrowserRouter>);
// …
Everything is working fine, but how can we ensure that our form component is redirecting to the new page after the api’s response?
一切工作正常,但如何确保api响应后表单组件重定向到新页面?
That’s a tricky question and I’ve been struggling with this. I’ve seen some complex solutions involving creating fake routers or mocking the react-router module. But there’s actually a pretty simple way to test this.
这是一个棘手的问题,我一直在为此苦苦挣扎。 我已经看到了一些复杂的解决方案,包括创建假路由器或模拟react-router模块。 但是实际上有一种非常简单的方法可以测试这一点。
If we try to snapshot our component after our API was called, we’ll notice that it renders an empty div.
如果在调用API之后尝试快照组件,则会注意到它呈现了一个空div。
expect(container).toMatchInlineSnapshot(`<div />`);
That’s because the redirection indeed happened, but there was no route to redirect to. From the testing-library renderer perspective, they are no routes defined, we just ask it to render and empty router containing the form.
那是因为确实发生了重定向,但是没有重定向到的路由。 从测试库渲染器的角度来看,它们没有定义路由,我们只是要求它渲染并清空包含表单的路由器。
To ensure that our user gets redirected to /book/1
(as the book's id returned by our API mock is 1
), we can add a route for that specific url with a simple text as children.
为了确保我们的用户被重定向到/book/1
(因为我们的API模拟返回的书的ID为1
),我们可以为该特定url添加一个路由,并以简单文本作为子级。
const {
container,
getByPlaceholderText,
getByText,
findByDisplayValue
} = render(
<BrowserRouter>
<BookCreateForm />
<Route path="/book/1">Book page</Route>
</BrowserRouter>
);
And test that the component rendered the text:
并测试该组件是否呈现了文本:
expect(container).toHaveTextContent(/Book page/);
Our final test :
我们的最终测试:
// BookCreateForm.test.js
import React from 'react';
import { render, act, fireEvent } from '@testing-library/react';
import { BrowserRouter, Route } from 'react-router-dom';
import BookCreateForm from './BookCreateForm';
import api from './api';
jest.mock('./api');
test('it calls api on form submit', async () => {
api.createBook = jest.fn(() => Promise.resolve({ id: 1 }));
const {
container,
getByPlaceholderText,
getByText,
findByDisplayValue
} = render(
<BrowserRouter>
<BookCreateForm />
<Route path="/book/1">Book page</Route>
</BrowserRouter>
);
await act(async () => {
const input = getByPlaceholderText(/Book's title/);
fireEvent.change(input, { target: { value: 'Yama Loka Terminus' }});
await findByDisplayValue(/Yama Loka Terminus/);
const button = getByText(/Create book/);
fireEvent.click(button);
});
expect(api.createBook).toHaveBeenCalledWith({ title: 'Yama Loka Terminus' });
expect(container).toHaveTextContent(/Book page/);
});
翻译自: https://medium.com/swlh/how-to-test-react-router-redirection-with-testing-library-c67504ddeec8