React - 如何在 Jest 中对 API 调用进行单元测试? [英] React - how do I unit test an API call in Jest?
问题描述
我有一堆 API 调用要进行单元测试.据我所知,单元测试 API 调用并不涉及实际进行这些 API 调用.据我所知,您会模拟这些 API 调用的响应,然后测试 DOM 更改,但是我目前正在努力做到这一点.我有以下代码:
App.js
function App() {const [text, setText] = useState("");函数 getApiData() {获取('/api').then(res => res.json()).then((结果) => {console.log(JSON.stringify(result));设置文本(结果);})}返回 (<div className="应用程序">{/* <button data-testid="modalButton" onClick={() =>modalAlter(true)}>显示模态</button>*/}<button data-testid="apiCall" onClick={() =>getApiData()}>进行 API 调用</button><p data-testid="ptag">{text}</p>
);}导出默认应用程序;
App.test.js
it('expect api call to change ptag', async() => {const fakeUserResponse = {'data': 'response'};var {getByTestId} = render( )var apiFunc = jest.spyOn(global, 'getApiData').mockImplementationOnce(() => {返回 Promise.resolve({json: () =>Promise.resolve(fakeUserResponse)})})fireEvent.click(getByTestId("apiCall"))const text = await getByTestId("ptag")期望(文本).toHaveTextContent(假用户响应['数据'])})
我试图在此处模拟 getApiData() 的结果,然后测试 DOM 更改(p 标记更改为结果).上面的代码给了我错误:
<块引用>无法窥探 getApiData 属性,因为它不是函数;取而代之的是未定义
如何访问该类函数?
我已经修改了代码,但还是有点问题:
App.js
function App() {const [text, setText] = useState("");异步函数 getApiData() {让结果 = 等待 API.apiCall()console.log("在反应端" + 结果)设置文本(结果 ['数据'])}返回 (<div className="应用程序">{/* <button data-testid="modalButton" onClick={() =>modalAlter(true)}>显示模态</button>*/}<button data-testid="apiCall" onClick={() =>getApiData()}>进行 API 调用</button><p data-testid="ptag">{text}</p>
);}导出默认应用程序;
apiController.js
export const API = {apiCall() {return fetch('/api').then(res => res.json())}}
Server.js
const express = require('express')const app = express()const https = require('https')常量端口 = 5000app.get('/api', (request, res) => {res.json("响应")})app.listen(port, () => console.log(`示例应用程序在 http://localhost:${port}`))
App.test.js
从'react'导入React;从'@testing-library/react'导入{渲染,浅,火事件};从'./App'导入应用程序;从 './apiController' 导入 {API}//从'酶'导入浅it('api调用返回一个字符串', async() => {const fakeUserResponse = {'data': 'response'};var apiFunc = jest.spyOn(API, 'apiCall').mockImplementationOnce(() => {返回 Promise.resolve({json: () =>Promise.resolve(fakeUserResponse)})})var {getByTestId, findByTestId} = render( )fireEvent.click(getByTestId("apiCall"))expect(await findByTestId("ptag")).toHaveTextContent('response');})
我得到的错误是
expect(element).toHaveTextContent()预期元素具有文本内容:回复已收到:14 |var {getByTestId, findByTestId} = render( )15 |fireEvent.click(getByTestId("apiCall"))>16 |expect(await findByTestId("ptag")).toHaveTextContent('response');|^17 |})18 |19 |//it('api调用返回一个字符串', async() => {
可重用单元测试(希望如此):
it('api调用返回一个字符串', async() => {const test1 = {'数据':'响应'};const test2 = {'数据':'错误'}var apiFunc = (响应) =>jest.spyOn(API, 'apiCall').mockImplementation(() => {console.log("响应" + JSON.stringify(response))返回 Promise.resolve(响应)})var {getByTestId, findByTestId} = render( )让 a = 等待 apiFunc(test1);fireEvent.click(getByTestId("apiCall"))expect(await findByTestId("ptag")).toHaveTextContent('response');让 b = 等待 apiFunc(test2);fireEvent.click(getByTestId("apiCall"))expect(await findByTestId("ptag")).toHaveTextContent('wrong');})
您无法访问 getApiData
,因为它是其他函数(闭包)中的私有函数,并且不公开给全局范围.这意味着 global
变量没有属性 getApiData
,而您得到的是 undefined given
.
要做到这一点,您需要以某种方式导出此函数,我建议将其移动到不同的文件,但同样的也应该没问题.这是一个简单的例子:
export const API = {获取数据(){return fetch('/api').then(res => res.json())}}
组件中的某处:
API.getData().then(result => setText(result))
在测试中:
var apiFunc = jest.spyOn(API, 'getData').mockImplementationOnce(() => {返回 Promise.resolve({json: () =>Promise.resolve(fakeUserResponse)})})
还有其他方法可以实现这一点,但也许这个就足够了.
而且我认为还有一个问题.您正在使用 const text = await getByTestId("ptag")
,但是 react-testing-library 中的 getBy*
函数不是异步的(它们不返回您可以的承诺)等待解决),因此您的测试将失败,因为您不会等待模拟请求完成.相反,请尝试 findBy*
此函数的版本,您可以await
并确保 promise 得到解决.
I have a bunch of API calls that I would like to unit test. As far as I know, unit testing API calls doesn't involve actually making those API calls. As far as I know you would simulate responses of those API calls and then test on the DOM changes however I'm currently struggling to do this. I have the following code:
App.js
function App() {
const [text, setText] = useState("");
function getApiData() {
fetch('/api')
.then(res => res.json())
.then((result) => {
console.log(JSON.stringify(result));
setText(result);
})
}
return (
<div className="App">
{/* <button data-testid="modalButton" onClick={() => modalAlter(true)}>Show modal</button> */}
<button data-testid="apiCall" onClick={() => getApiData()}>Make API call</button>
<p data-testid="ptag">{text}</p>
</div>
);
}
export default App;
App.test.js
it('expect api call to change ptag', async () => {
const fakeUserResponse = {'data': 'response'};
var {getByTestId} = render(<App />)
var apiFunc = jest.spyOn(global, 'getApiData').mockImplementationOnce(() => {
return Promise.resolve({
json: () => Promise.resolve(fakeUserResponse)
})
})
fireEvent.click(getByTestId("apiCall"))
const text = await getByTestId("ptag")
expect(text).toHaveTextContent(fakeUserResponse['data'])
})
I'm trying to mock the result of getApiData() here and then test a DOM change (the p tag changes to the result). The above code gives me the error:
Cannot spy the getApiData property because it is not a function; undefined given instead
How do I access that class function?
EDIT:
I've adapted the code but I'm still having a bit of trouble:
App.js
function App() {
const [text, setText] = useState("");
async function getApiData() {
let result = await API.apiCall()
console.log("in react side " + result)
setText(result['data'])
}
return (
<div className="App">
{/* <button data-testid="modalButton" onClick={() => modalAlter(true)}>Show modal</button> */}
<button data-testid="apiCall" onClick={() => getApiData()}>Make API call</button>
<p data-testid="ptag">{text}</p>
</div>
);
}
export default App;
apiController.js
export const API = {
apiCall() {
return fetch('/api')
.then(res => res.json())
}
}
Server.js
const express = require('express')
const app = express()
const https = require('https')
const port = 5000
app.get('/api', (request, res) => {
res.json("response")
})
app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`))
App.test.js
import React from 'react';
import { render, shallow, fireEvent } from '@testing-library/react';
import App from './App';
import {API} from './apiController'
//import shallow from 'enzyme'
it('api call returns a string', async () => {
const fakeUserResponse = {'data': 'response'};
var apiFunc = jest.spyOn(API, 'apiCall').mockImplementationOnce(() => {
return Promise.resolve({
json: () => Promise.resolve(fakeUserResponse)
})
})
var {getByTestId, findByTestId} = render(<App />)
fireEvent.click(getByTestId("apiCall"))
expect(await findByTestId("ptag")).toHaveTextContent('response');
})
The error I'm getting is
expect(element).toHaveTextContent()
Expected element to have text content:
response
Received:
14 | var {getByTestId, findByTestId} = render(<App />)
15 | fireEvent.click(getByTestId("apiCall"))
> 16 | expect(await findByTestId("ptag")).toHaveTextContent('response');
| ^
17 | })
18 |
19 | // it('api call returns a string', async () => {
Reusable unit test (hopefully):
it('api call returns a string', async () => {
const test1 = {'data': 'response'};
const test2 = {'data': 'wrong'}
var apiFunc = (response) => jest.spyOn(API, 'apiCall').mockImplementation(() => {
console.log("the response " + JSON.stringify(response))
return Promise.resolve(response)
})
var {getByTestId, findByTestId} = render(<App />)
let a = await apiFunc(test1);
fireEvent.click(getByTestId("apiCall"))
expect(await findByTestId("ptag")).toHaveTextContent('response');
let b = await apiFunc(test2);
fireEvent.click(getByTestId("apiCall"))
expect(await findByTestId("ptag")).toHaveTextContent('wrong');
})
You cannot access getApiData
because it's a private function inside other function (a closure) and it's not exposed to the global scope. That means global
variable does not have property getApiData
, and you are getting undefined given instead
.
To do this you need to export somehow this function, I would suggest by moving it to different file, but the same should be fine as well. Here's a simple example:
export const API = {
getData() {
return fetch('/api').then(res => res.json())
}
}
Somewhere in your component:
API.getData().then(result => setText(result))
And in test:
var apiFunc = jest.spyOn(API, 'getData').mockImplementationOnce(() => {
return Promise.resolve({
json: () => Promise.resolve(fakeUserResponse)
})
})
There are other ways to achieve that, but maybe this one would be enough.
And I think there would be one more problem. You are using const text = await getByTestId("ptag")
, but getBy*
functions from react-testing-library are not asynchronous (they do not return a promise you can wait to resolve), so your test will fail, as you wouldn't wait for a mock request to finish. Instead, try findBy*
version of this function that you can await
on and make sure promise is resolved.
这篇关于React - 如何在 Jest 中对 API 调用进行单元测试?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!