React Native 测试 - 无需等待即可行动

Posted

技术标签:

【中文标题】React Native 测试 - 无需等待即可行动【英文标题】:React Native testing - act without await 【发布时间】:2021-03-05 05:24:58 【问题描述】:

以下测试通过,但我两次收到以下警告,我不知道为什么。有人可以帮我弄清楚吗?

    console.error
    Warning: You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);
      at printWarning (../../node_modules/react-test-renderer/cjs/react-test-renderer.development.js:120:30)
      at error (../../node_modules/react-test-renderer/cjs/react-test-renderer.development.js:92:5)
      at ../../node_modules/react-test-renderer/cjs/react-test-renderer.development.js:14953:13
      at tryCallOne (../../node_modules/react-native/node_modules/promise/lib/core.js:37:12)
      at ../../node_modules/react-native/node_modules/promise/lib/core.js:123:15
      at flush (../../node_modules/asap/raw.js:50:29)
import  fireEvent  from '@testing-library/react-native'
import  renderScreen  from 'test/render'

describe('screens/home', () => 
  it('should render and redirect to the EventScreen', async () => 
    const 
      getByA11yLabel,
      findByA11yLabel,
      findAllByA11yLabel,
      toJSON
     = renderScreen('Main')
    expect(toJSON()).toMatchSnapshot('Default render')

    const title = 'New event'
    const titleInput = getByA11yLabel('event.title')

    // Change title - sync fn
    fireEvent.changeText(titleInput, title)

    // Create button should be visible
    const createButton = await findByA11yLabel('event.create')
    expect(titleInput.props.value).toBe(title)
    expect(createButton).toBeTruthy()
    expect(toJSON()).toMatchSnapshot('Change title')

    // Create event - async fn
    fireEvent.press(createButton)

    // The app should be redirected to the EventScreen
    const titleInputs = await findAllByA11yLabel('event.title')
    const upsertButton = await findByA11yLabel('event.upsert')
    expect(toJSON()).toMatchSnapshot('Create event')
    expect(titleInputs).toHaveLength(2)
    expect(titleInputs[0].props.value).toBe('') // @MainScreen
    expect(titleInputs[1].props.value).toBe(title) // @EventScreen
    expect(upsertButton).toBeTruthy()
  )
)
据我所知,没有必要用act-link 包裹fireEvent findBy* 也自动用 act 包裹 - link 相关issue in GitHub 仍然开放

依赖关系:

反应:16.13.1 世博会:39.0.4 开玩笑:26.6.3 ts-jest:26.4.4 jest-expo: 39.0.0 @testing-library/jest-native: 3.4.3 @testing-library/react: 11.2.2 @testing-library/react-native: 7.1.0 反应测试渲染器:16.13.1 打字稿:4.1.2

【问题讨论】:

【参考方案1】:

如果您已经用尽所有其他调试工作并且非常确定您的代码编写正确,则可能与 react-native/jest-preset 用模拟替换 global.Promise 相关(请参阅 issue)。

在这种情况下,问题的解决方案是覆盖/修补 jest 预设以首先保存原始全局 Promise,应用 react-native/jest-preset 然后恢复原始 Promise(覆盖模拟版本)。这让我可以在与渲染无关的测试中使用await,而不会触发可怕的

console.error
Warning: You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);

此 sn-p 显示了执行此修补程序的一种方法:https://github.com/sbalay/without_await/commit/64a76486f31bdc41f5c240d28263285683755938

【讨论】:

这听起来不错,但我正在使用 jest-expo,这个解决方案对我没有帮助。【参考方案2】:

我也遇到了同样的问题。对于我的情况,我在我的组件中使用了useEffect。在测试时,它提示我将渲染包装在 act() 调用中。一旦我这样做了,即act(async () => ...),我最初的问题就解决了,但我得到了上述错误(Warning: You called act(async () => ...) without await.)。我不得不在我的测试中使用await act(async () => ...) 来解决这个问题。虽然我仍然不确定为什么需要它。

作为参考,我正在使用await act(async () => ...);添加一个完整的示例组件和相应的测试

LocationComponent.tsx

/** @jsx jsx */
import  jsx  from 'theme-ui';
import  FunctionComponent, useEffect, useState  from 'react';

type Coordinate = 
  latitude: number;
  longitude: number;
;

const LocationComponent: FunctionComponent<any> = () => 
  const [coordinate, setCoordinate] = useState<Coordinate>();
  const [sharedLocation, setSharedLocation] = useState<boolean>();
  useEffect(() => 
    let mounted = true;

    if (!coordinate && navigator) 
      navigator.geolocation.getCurrentPosition(function (position) 
        setCoordinate(
          latitude: position.coords.latitude,
          longitude: position.coords.longitude,
        );
      );
      navigator.permissions
        .query( name: 'geolocation' )
        .then(function (result) 
          if (mounted) setSharedLocation(result.state === 'granted');
        );
    

    return () => (mounted = false);
  );

  return (
    <>
      <div>Location shared:sharedLocation ? 'Yes' : 'No'</div>
      <div>Latitude:coordinate?.latitude</div>
      <div>Longitude:coordinate?.longitude</div>
    </>
  );
;
export default LocationComponent;

LocationComponent.spec.tsx

import React from 'react';
import  render, waitFor  from '@testing-library/react';
import  act  from 'react-dom/test-utils';
import LocationComponent from '../../../../../src/components/scheduler/location/LocationComponent';

const TEST_COORDS = 
  latitude: 41.8817089,
  longitude: -87.643301,
;

global.navigator.permissions = 
  query: jest
    .fn()
    .mockImplementationOnce(() => Promise.resolve( state: 'granted' )),
;

global.navigator.geolocation = 
  getCurrentPosition: jest.fn().mockImplementationOnce((success) =>
    Promise.resolve(
      success(
        coords: TEST_COORDS,
      )
    )
  ),
;

describe("Location Component when location share is 'granted'", () => 
  it('should display current location details', async () => 
    await act(async () => 
      const  getByText  = render(<LocationComponent />);

      /*expect(
        await waitFor(() => getByText('Location shared:Yes'))
      ).toBeInTheDocument();*/
      expect(
        await waitFor(() => getByText('Latitude:41.8817089'))
      ).toBeInTheDocument();
      expect(
        await waitFor(() => getByText('Longitude:-87.643301'))
      ).toBeInTheDocument();
    );
  );
);

【讨论】:

以上是关于React Native 测试 - 无需等待即可行动的主要内容,如果未能解决你的问题,请参考以下文章

无需等待 React JS 中的 API 数据即可进行渲染

无需导入即可使用类型和接口

react native 入门 - 环境搭建, 创建第一个Hello World

React-native 支持 RTL 和 LTR,无需更改设备语言

React Native 一键登录

是否已经可以在 react-native 中使用***等待?