問題描述
如何在使用反應測試庫單擊父組件上的按鈕後測試子組件呈現文本? (How to test the child component render text after the button on the parent component click with react testing library?)
我有一個父組件 App
並且它有一個按鈕。單擊按鈕時,它將調用 axios
來獲取帖子,然後渲染帖子項目。App
有一個子組件 Post
,Post
有一個子組件 ListItem
。在我的 App 測試文件中,我測試了 axios 在單擊按鈕後可以正確加載。但是,我無法測試呈現的內容。它找不到 data‑testid
:Unable to find an element by: [data‑testid="test‑axios‑content"]
。
我使用 react 測試庫
。
這是我的測試文件:
import React from "react"
import App from "../../App"
import { render, fireEvent, screen, waitFor, act } from "../utils/test‑utils"
import axios from "axios"
jest.mock("axios", () => {
return {
get: jest.fn()
}
})
describe("App test", () => {
afterEach(() => {
jest.resetAllMocks()
})
it("should load and display the data", async () => {
const { getByTestId } = render(<App />)
axios.get.mockResolvedValueOnce({
data: { title: "hello there", body: "abc" }
})
await act(async () => {
fireEvent.click(getByTestId("test‑axios‑button"))
expect(axios.get).toHaveBeenCalledTimes(1)
const testingData = await waitFor(() => getByTestId("test‑axios‑content"))
expect(testingData).toHaveTextContent("hello there")
})
})
})
</code></pre>
第一個 axios 調用時間是正確的,但我的測試找不到 testId test‑axios‑content
。我把它放在 App.js 上的子組件上。
App.js
...
function App() {
const [posts, setPosts] = useState([])
const handleClick= async () => {
const postsResult = await getPosts()
setPosts(postsResult.data)
}
return (
<div className="App" data‑test="appComponent">
<button data‑testid="test‑axios‑button" onClick={handleClick}>
get post from axios
</button>
<section>
<div>Load Posts:</div>
<Post posts={posts} data‑testid="test‑axios‑content" />
</section>
</div>
}
...
api getPosts:
import axios from "axios"
export const getPosts = async () => await axios.get("https://jsonplaceholder.typicode.com/posts?_limit=10")
Post:
import ListItem from "../listItem"
const Post= (props) => {
const posts = props.posts
return (
<>
{posts.length > 0 && (
<div>
{posts.map((post, index) => {
const { title, body } = post
return <ListItem key={title} title={title} desc={body} />
})}
</div>
)}
</>
)
}
export default Post
</code></pre>
列表項:
const ListItem = (props) => {
const { title, desc } = props
return (
<div>
<h2 data‑test="title" data‑testid="title">
{title}
</h2>
<div data‑test="desc" data‑testid="desc">
{desc}
</div>
</div>
)
}
export default ListItem
</code></pre>
參考解法
方法 1:
The first wrong part is that posts
is one array, but on the axios mock
, it's one object: { title: "hello there", body: "abc" }
. The correct format is : [{ title: "hello there", body: "abc" }]
So, on the axios mock
part of testing, the correct code is:
axios.get.mockResolvedValueOnce({
data: [{ title: "hello there", body: "abc" }]
})
The second wrong part is rather than using await act
, it should use trigger click first and then use await waitFor
with a callback:
fireEvent.click(getByTestId("test‑axios‑button"))
await waitFor(() => {
expect(axios.get).toHaveBeenCalledTimes(1)
const renderData = screen.getByText("hello there")
expect(renderData).toBeInTheDocument()
})
This is the final:
it("should load and display the data", async () => {
const { getByTestId } = render(<App />)
axios.get.mockResolvedValueOnce({
data: [{ title: "hello there", body: "abc" }]
})
fireEvent.click(getByTestId("test‑axios‑button"))
await waitFor(() => {
expect(axios.get).toHaveBeenCalledTimes(1)
const testingData = await waitFor(() => getByTestId("test‑axios‑content"))
expect(testingData).toHaveTextContent("hello there")
})
})
參考文件