React Native测试库:如何用动作检验使用效果

mgdq6dx1  于 2023-03-09  发布在  React
关注(0)|答案(8)|浏览(121)

我正在使用react-native-testing-library来测试我的react-native组件。我有一个组件(为了本文的目的,它被过度简化了):

export const ComponentUnderTest = () => {

 useEffect(() => {
   __make_api_call_here_then_update_state__
 }, [])

 return (
   <View>
     __content__goes__here
   </View>
 )
}

下面是我的(简化的)component.spec.tsx

import { render, act } from 'react-native-testing-library';
import { ComponentUnderTest } from './componentundertest.tsx';

test('it updates content on successful call', () => {
   let root;
   act(() => {
      root = render(<ComponentUnderTest />); // this fails with below error message
   });    
   expect(...);
})

现在当我运行这段代码时,我得到了这个错误:Can't access .root on unmounted test renderer

我甚至不知道这个错误消息是什么意思。我按照react-native-testing-library的文档来测试act and useEffect
任何帮助都将不胜感激。谢谢

zte4gxcn

zte4gxcn1#

我找到了一个变通办法:

import { render, waitFor } from 'react-native-testing-library';
import { ComponentUnderTest } from './componentundertest.tsx';

test('it updates content on successful call', async () => {
   const root = await waitFor(() =>
       render(<ComponentUnderTest />);
   );   
   expect(...);
})
f5emj3cl

f5emj3cl2#

您可以使用以下命令执行此操作:@测试库/React Native
示例:

import { cleanup, fireEvent, render, debug, act} from '@testing-library/react-native'

afterEach(() => cleanup());

test('given correct credentials, gets response token.', async () => {
    const { debug, getByPlaceholderText, getByRole } = await render(<Component/>);

    await act( async () => {
            const emailInput = getByPlaceholderText('Email');;
            const passwordInput = getByPlaceholderText('Password');
            const submitBtn = getByRole('button', {name: '/submitBtn/i'});

            fireEvent.changeText(emailInput, 'email');
            fireEvent.changeText(passwordInput, 'password');
            fireEvent.press(submitBtn);
    });
});

应该也可以使用useEffect,但是我还没有自己测试过。使用useState效果很好。

mmvthczy

mmvthczy3#

root = render(<ComponentUnderTest />);

应该是

root = create(<ComponentUnderTest />);

----完整的代码片段。它为我工作后,上述变化

import React, { useState, useEffect } from 'react'
import { Text, View } from 'react-native'
import { render, act } from 'react-native-testing-library'
import { create } from 'react-test-renderer'

export const ComponentUnderTest = () => {
  useEffect(() => {}, [])

  return (
    <View>
      <Text>Hello</Text>
    </View>
  )
}

test('it updates content on successful call', () => {
  let root
  act(() => {
    root = create(<ComponentUnderTest />) 
  })
})
xjreopfe

xjreopfe4#

以下步骤解决了我的问题:

  • Reactreact-test-renderer版本升级到16.9或更高版本,在act中支持async函数(据我所知,两个软件包需要是同一版本)
  • 按照@helloworld的建议将react-native-testing-libraryrender替换为react-test-renderercreate(谢谢好心的先生,它帮了我的忙)
  • 生成测试函数async,在act之前添加await,并向其传递async函数

最后的结果是这样的:

test('it updates content on successful call', async () => {
  let root
  await act(async () => {
    root = create(<ComponentUnderTest />) 
  })
})
gc0ot86w

gc0ot86w5#

我使用useEffect来测试异步组件(该组件会触发setState的重新呈现)的方法是将测试用例设置为正常,但使用waitFor or findBy来阻塞Assert,直到组件使用获取的数据重新呈现。
下面是一个简单的、可运行的示例:

import React, {useEffect, useState} from "react";
import {FlatList, Text} from "react-native";
import {render} from "@testing-library/react-native";

const Posts = () => {
  const [posts, setPosts] = useState(null);

  useEffect(() => {
    const url = "https://jsonplaceholder.typicode.com/posts";
    fetch(url).then(res => res.json()).then(setPosts);
  }, []);

  return !posts ? <Text>loading</Text> : <FlatList
    testID="posts"
    data={posts}
    renderItem={({item: {id, title}, index}) =>
      <Text testID="post" key={id}>{title}</Text>
    }
  />;
};

describe("Posts", () => {
  beforeEach(() => {
    global.fetch = jest.fn(url => Promise.resolve({
      ok: true,
      status: 200,
      json: () => Promise.resolve([
        {id: 1, title: "foo title"},
        {id: 2, title: "bar title"},
      ])
    }));
  });

  it("should fetch posts", async () => {
    const {findAllByTestId} = render(<Posts />);
    const posts = await findAllByTestId("post", {timeout: 500});
    expect(posts).toHaveLength(2);
    expect(posts[0]).toHaveTextContent("foo title");
    expect(posts[1]).toHaveTextContent("bar title");
    expect(fetch).toHaveBeenCalledTimes(1);
  });
});

这没有给予我任何act警告,但我也有过这样的警告。This open GitHub issue似乎是规范的资源。
使用的 Package :

{
  "dependencies": {
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-native": "^0.64.0",
    "react-native-web": "^0.15.6"
  },
  "devDependencies": {
    "@babel/core": "^7.13.15",
    "@testing-library/jest-native": "^4.0.1",
    "@testing-library/react-native": "^7.2.0",
    "babel-jest": "^26.6.3",
    "jest": "^26.6.3",
    "metro-react-native-babel-preset": "^0.65.2",
    "react-test-renderer": "^17.0.2"
  }
}

在Jest配置中:

setupFilesAfterEnv: ["@testing-library/jest-native/extend-expect"],

用于.toHaveTextContent匹配器。或者,您可以使用导入:

import "@testing-library/jest-native/extend-expect";
4bbkushb

4bbkushb6#

这样试试看

it("should render <Component/>", async () => {
  await act(() => {
    render(<Activate />);
  });
});
5tmbdcev

5tmbdcev7#

您可以非常轻松地在RNTL测试中使用useEffects

import { render, act } from '@testing-library/react-native';
import { ComponentUnderTest } from './componentundertest.tsx';

test('it updates content on successful call', () => {
   render(<ComponentUnderTest />)  
   expect(await screen.findByText('Results)).toBeTruthy(); // A
})

不需要直接使用act,RNTL在钩子下为您使用它。
A行上使用的确切 predicate 取决于您在useEffect回调中所做的组件更改,这里我假设当获取成功时,有某个Text组件显示“Results”文本。
需要注意的重要一点是,您的获取可能是异步的,因此您需要使用findBy*查询,它将等待异步操作发生(默认超时为~5000毫秒,可以调整)。
另一件需要注意的事情是,模拟网络调用是一个很好的做法,这样你的测试就不会调用真正的API。这有很多原因,测试执行速度,测试稳定性,不总是能够达到测试目的所需的API响应,等等。推荐的工具是MSW库。

00jrzges

00jrzges8#

    • 您需要使用waitFor来等待异步请求完成。**

下面是一个更新的代码片段及其说明:

import { render, waitFor } from 'react-native-testing-library';
import { ComponentUnderTest } from './componentundertest.tsx';

test('it updates content on successful call', async () => {

  // Mocking a successful API response
  yourMockApi.get.mockResolvedValue({});

  // Rendering the component under test
  render(<ComponentUnderTest />);

  // Wait for the API call to be made
  await waitFor(() => expect(yourMockApi.get).toBeCalled());
});

说明:

  • 正在模拟yourMockApi.get方法以使用mockResolvedValue返回成功响应。
  • waitFor函数用于等待模拟API调用完成后再继续测试。
  • await关键字用于等待waitFor函数完成,然后再继续测试。

相关问题