【问题标题】:React - how do I unit test an API call in Jest?React - 如何在 Jest 中对 API 调用进行单元测试?
【发布时间】:2020-08-17 22:45:03
【问题描述】:

我有一堆 API 调用要进行单元测试。据我所知,单元测试 API 调用并不涉及实际进行这些 API 调用。据我所知,您将模拟这些 API 调用的响应,然后测试 DOM 更改,但是我目前正在努力做到这一点。我有以下代码:

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'])
})

我试图在此处模拟 getApiData() 的结果,然后测试 DOM 更改(p 标签更改为结果)。上面的代码给了我错误:

无法窥探 getApiData 属性,因为它不是函数;取而代之的是未定义

如何访问该类函数?

编辑:

我已经修改了代码,但还是有点麻烦:

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())
    }
}

服务器.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');
})

我得到的错误是

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 () => {

可重用的单元测试(希望如此):

    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');

    })

【问题讨论】:

    标签: reactjs unit-testing jestjs enzyme


    【解决方案1】:

    您无法访问getApiData,因为它是其他函数(闭包)中的私有函数,并且不暴露在全局范围内。这意味着global 变量没有属性getApiData,而你得到的是undefined given instead

    要做到这一点,你需要以某种方式导出这个函数,我建议将它移动到不同的文件,但同样应该没问题。这是一个简单的例子:

    export const API = {
      getData() {
        return fetch('/api').then(res => res.json())
      }
    }
    

    组件中的某处:

    API.getData().then(result => setText(result))
    

    在测试中:

    var apiFunc = jest.spyOn(API, 'getData').mockImplementationOnce(() => {
        return Promise.resolve({
          json: () => Promise.resolve(fakeUserResponse)
        })
      })
    

    还有其他方法可以实现这一点,但也许这个就足够了。

    而且我认为还有一个问题。您正在使用 const text = await getByTestId("ptag"),但 react-testing-library 中的 getBy* 函数不是异步的(它们不会返回您可以等待解决的承诺),因此您的测试将失败,因为您不会等待模拟要求完成。相反,请尝试 findBy* 版本的此函数,您可以在 await 上打开并确保 promise 已解决。

    【讨论】:

    • 感谢您的帮助。我已经相应地编辑了示例,但是我遇到了一些麻烦。 ptag 似乎没有收到任何文本输出。
    • 您的 spy 不正确 - 您在模拟 json() 方法,但不是这个级别(使用此模拟,您将完全删除对 json() 的调用)。请改用此版本:jest.spyOn(API, "apiCall").mockImplementation(() =&gt; Promise.resolve(fakeUserResponse));
    • 非常感谢您的帮助,您对我的帮助很大。你知道用多个 fakeUserResponse 测试用例来尝试这个的好方法是什么吗?
    • 没问题,很高兴我能帮上忙 :)
    • 很抱歉再次纠缠你,但你知道用多个 fakeUserResponse 测试用例来尝试这个的好方法是什么吗?
    【解决方案2】:

    不要模拟 API 库。最好使用服务器响应的存根。如果您编写了一堆模拟 API 调用的测试,那么您将应用程序的实现绑定到您的测试。假设您不想使用fetch(),但想将isomorphic-unfetch 之类的东西用于SSR 应用程序?切换整个模拟测试套件会非常痛苦。

    改为使用服务器存根库,例如 nockmsw。将这些库视为 JSDOM,但用于您的服务器。这样,您将测试套件绑定到后端而不是实现库。让我们重写您的示例以向您展示我的意思:

    import React from 'react';
    import nock from 'nock';
    import { render, shallow, fireEvent } from '@testing-library/react';
    
    import App from './App';
    
    it('displays user data', async () => {
      const scope = nock('https://yoursite.com')
        .get('/api')
        .once()
        .reply(200, {
          data: 'response',
        });
    
      var {getByTestId, findByTestId} = render(<App />)
      fireEvent.click(getByTestId("apiCall"))
      expect(await findByTestId("ptag")).toHaveTextContent('response');
    })
    

    查看我写的博客文章,以更深入地了解该主题,Testing components that make API calls

    【讨论】:

      猜你喜欢
      • 2020-10-03
      • 2020-07-24
      • 2019-06-08
      • 2016-07-04
      • 1970-01-01
      • 2018-08-18
      • 2017-12-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多