使用 react-testing-library 在 useEffect 中测试 api 调用

Posted

技术标签:

【中文标题】使用 react-testing-library 在 useEffect 中测试 api 调用【英文标题】:Testing api call inside useEffect using react-testing-library 【发布时间】:2020-05-10 12:48:49 【问题描述】:

我想测试应该显示在我的功能组件中的 api 调用和返回的数据。我创建了执行 api 调用的 List 组件。我希望返回的数据显示在组件中,为此我使用了 useState 挂钩。组件如下所示:

const List: FC<> = () => 
    const [data, setData] = useState<number>();
    const getData = (): Promise<any> => 
        return fetch('https://jsonplaceholder.typicode.com/todos/1');
    ;

    React.useEffect(() => 
        const func = async () => 
            const data = await getData();
            const value = await data.json();
            setData(value.title);
        
        func();
    , [])

    return (
        <div>
            <div id="test">data</div>
        </div>
    )

我写了一个测试来模拟 fetch 方法。我检查是否调用了 fetch 方法并且它确实发生了。不幸的是,我不知道如何测试从响应返回的值。当我尝试 console.log 时,我只是得到 null 并且我想得到“示例文本”。我的猜测是我必须等待从 Promise 返回的这个值。不幸的是,尽管尝试了方法行为和等待,但我不知道如何实现它。这是我的测试:

it('test', async () => 
    let component;
    const fakeResponse = 'example text';
    const mockFetch = Promise.resolve(json: () => Promise.resolve(fakeResponse));
    const mockedFetch = jest.spyOn(window, 'fetch').mockImplementationOnce(() => mockFetch as any )
    await wait( async () => 
        component = render(<List />);
    )
    const value: Element = component.container.querySelector('#test');
    console.log(value.textContent);
    expect(mockedFetch).toHaveBeenCalledTimes(1);
)

如果有任何建议,我将不胜感激。

第二次尝试

还尝试使用data-testid="test"waitForElement,但仍然收到空值。

更新的组件增量:

  const List: FC<> = () => 
-     const [data, setData] = useState<number>();
+     const [data, setData] = useState<string>('test');
      const getData = (): Promise<any> => 
          return fetch('https://jsonplaceholder.typicode.com/todos/1');
      ;
  
      React.useEffect(() => 
          const func = async () => 
              const data = await getData();
              const value = await data.json();
              setData(value.title);
          
          func();
      , [])
  
      return (
          <div>
-             <div id="test">data</div>
+             <div data-testid="test" id="test">data</div>
          </div>
      )
  

和更新的测试:

it('test', async () => 
    const fakeResponse = 'example text';
    const mockFetch = Promise.resolve(json: () => Promise.resolve(fakeResponse));
    const mockedFetch = jest.spyOn(window, 'fetch').mockImplementationOnce(() => mockFetch as any )
    const  getByTestId  = render(<List />);
    expect(getByTestId("test")).toHaveTextContent("test");
    const resolvedValue = await waitForElement(() => getByTestId('test'));
    expect(resolvedValue).toHaveTextContent("example text");
    expect(mockedFetch).toHaveBeenCalledTimes(1);
)

【问题讨论】:

好的,在我的代码和测试中一切都很好。只是我在模拟数据中犯了一个错误。在我的组件中,我试图访问标题键。在测试中,我嘲笑了一个愚蠢的字符串,这就是我收到空值的原因。 在谷歌搜索中找到了这个——我建议用最终版本更新你的问题。看起来您的帖子有几个不同的变体 偶然发现这个,同样有兴趣知道这个断言是否通过了 `expect(mockedFetch).toHaveBeenCalledTimes(1); ` 【参考方案1】:

这是一个有效的单元测试示例:

index.tsx:

import React,  useState, FC  from 'react';

export const List: FC<> = () => 
  const [data, setData] = useState<number>();
  const getData = (): Promise<any> => 
    return fetch('https://jsonplaceholder.typicode.com/todos/1');
  ;

  React.useEffect(() => 
    const func = async () => 
      const data = await getData();
      const value = await data.json();
      setData(value.title);
    ;
    func();
  , []);

  return (
    <div>
      <div data-testid="test">data</div>
    </div>
  );
;

index.test.tsx:

import  List  from './';
import React from 'react';
import '@testing-library/jest-dom/extend-expect';
import  render, waitForElement  from '@testing-library/react';

describe('59892259', () => 
  let originFetch;
  beforeEach(() => 
    originFetch = (global as any).fetch;
  );
  afterEach(() => 
    (global as any).fetch = originFetch;
  );
  it('should pass', async () => 
    const fakeResponse =  title: 'example text' ;
    const mRes =  json: jest.fn().mockResolvedValueOnce(fakeResponse) ;
    const mockedFetch = jest.fn().mockResolvedValueOnce(mRes as any);
    (global as any).fetch = mockedFetch;
    const  getByTestId  = render(<List></List>);
    const div = await waitForElement(() => getByTestId('test'));
    expect(div).toHaveTextContent('example text');
    expect(mockedFetch).toBeCalledTimes(1);
    expect(mRes.json).toBeCalledTimes(1);
  );
);

单元测试结果:

 PASS  src/***/59892259/index.test.tsx (9.816s)
  59892259
    ✓ should pass (63ms)

-----------|----------|----------|----------|----------|-------------------|
File       |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
-----------|----------|----------|----------|----------|-------------------|
All files  |      100 |      100 |      100 |      100 |                   |
 index.tsx |      100 |      100 |      100 |      100 |                   |
-----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        11.73s, estimated 13s

【讨论】:

以上是关于使用 react-testing-library 在 useEffect 中测试 api 调用的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 React-Testing-Library 测试 mapDispatchToProps?

如何使用 Jest 和 react-testing-library 测试 useRef?

react-testing-library - 屏幕与渲染查询

如何使用 react-testing-library 测试由其他组件组成的组件?

使用 react-testing-library 时如何测试组件是不是使用正确的道具呈现?

使用 react-testing-library 时找不到带有文本的元素:“myText”错误