React - 如何在 Jest 中对 API 调用进行单元测试? [英] React - how do I unit test an API call in Jest?

查看:38
本文介绍了React - 如何在 Jest 中对 API 调用进行单元测试?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一堆 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屋!

查看全文
相关文章
其他开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆