使用 react-testing-library 进行测试时可以手动触发状态更改吗? [英] Can I manually trigger a state change when testing using react-testing-library?

查看:21
本文介绍了使用 react-testing-library 进行测试时可以手动触发状态更改吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我仍在将我的 Enzyme 测试转移到 react-testing-library 的过程中,我有一个相当常见的场景,当一个组件挂载时,它会启动一个 Ajax 请求来获取一些数据.就在获取开始之前,它设置一些状态值以指示它正在加载,这反过来呈现一个微调器.完成后,状态将更新为数据,并在适当的情况下将loadingState"设置为Completed"或Failed".

import React, { useEffect, useState } from "react";从react-spinners-kit"导入 { SwapSpinner };从样式组件"导入样式;从ramda"导入*为R;从./getPeople"导入 { getPeople };const FlexCenter = styled.div`高度:250px;显示:弹性;对齐内容:居中;对齐项目:居中;`;常量加载状态 = {notStarted: "notStarted",isLoading: "isLoading",成功:成功",失败:失败"};功能应用(){const [people, setPeople] = useState([]);const [isLoading, setLoading] = useState(loadingStates.notStarted);useEffect(() => {setLoading(loadingStates.isLoading);获取人().then(({结果}) => {设置人(结果);setLoading(loadingStates.success);}).catch(错误=> {setLoading(loadingStates.failure);});}, []);返回 (<div>{R.cond([[R.equals(loadingStates.isLoading),() =>(<FlexCenter data-testid="spinner"><SwapSpinner/></FlexCenter>)],[R.equals(loadingStates.success),() =>(<ul data-testid="people-list">{people.map(({ name }) => (<li key={name}>{name}</li>))})],[R.equals(loadingStates.failure),<div>发生错误</div>]])(isLoading)}

);}导出默认应用程序;

使用 Enzyme,我可以手动将状态设置为任意一个 loadingStates 键,并断言渲染条件会呈现适当的更改.

有没有办法在 RTL 中做到这一点?

解决方案

你不能用 RTL 做到这一点.您不应该与组件的内部交互.

这大概是我测试你的组件的方式:

import { getPeople } from "./getPeople";jest.mock('./getPeople')test('测试的骨架', async () => {const people = [/* 在这里放一些模拟人 */]getPeople.mockResolvedValueOnce({ 结果:人 })渲染(<应用程序/>)expect(/* 以某种方式获得加载微调器 */).toBeInTheDocument()await wait(() => expect(/* 这里你检查人在页面上 */).toBeInTheDocument())//我们还检查 API 是否被调用期望(getPeople).toHaveBeenCalledOnce()期望(getPeople).toHaveBeenCalledWith()})

如您所见,我不是在检查 App 的内部状态.相反,我正在检查是否显示了一个加载微调器,然后人们出现在屏幕上并且 API 被调用.

此测试更可靠,因为您正在测试用户将看到的内容,而不是实现细节.

I'm still in the process of moving my Enzyme tests over to react-testing-library, and I have a fairly common scenario where, when a component mounts, it kicks off an Ajax request to get some data. Just before the fetch starts, it sets some state value to indicate it is loading, which in turn renders a spinner. When it's finished, the state is updated with both the data, and "loadingState" is set to "Completed" or "Failed", where appropriate.

import React, { useEffect, useState } from "react";
import { SwapSpinner } from "react-spinners-kit";
import styled from "styled-components";
import * as R from "ramda";

import { getPeople } from "./getPeople";

const FlexCenter = styled.div`
  height: 250px;
  display: flex;
  justify-content: center;
  align-items: center;
`;

const loadingStates = {
  notStarted: "notStarted",
  isLoading: "isLoading",
  success: "success",
  failure: "failure"
};

function App() {
  const [people, setPeople] = useState([]);
  const [isLoading, setLoading] = useState(loadingStates.notStarted);

  useEffect(() => {
    setLoading(loadingStates.isLoading);
    getPeople()
      .then(({ results }) => {
        setPeople(results);
        setLoading(loadingStates.success);
      })
      .catch(error => {
        setLoading(loadingStates.failure);
      });
  }, []);

  return (
    <div>
      {R.cond([
        [
          R.equals(loadingStates.isLoading),
          () => (
            <FlexCenter data-testid="spinner">
              <SwapSpinner />
            </FlexCenter>
          )
        ],
        [
          R.equals(loadingStates.success),
          () => (
            <ul data-testid="people-list">
              {people.map(({ name }) => (
                <li key={name}>{name}</li>
              ))}
            </ul>
          )
        ],
        [R.equals(loadingStates.failure), <div>An error occured</div>]
      ])(isLoading)}
    </div>
  );
}

export default App;

With Enzyme, I could manually set the state to any one of the loadingStates keys, and assert that the render conditional renders the appropriate changes.

Is there a way that I can do this in RTL?

解决方案

You can not do that with RTL. You are not supposed to interact with the internals of your components.

This is roughly how I would test your component:

import { getPeople } from "./getPeople";
jest.mock('./getPeople')

test('skeleton of a test', async () => {
  const people = [/* Put some mock people in here */]
  getPeople.mockResolvedValueOnce({ results: people })
  render(<App />)

  expect(/* Somehow get the loading spinner */).toBeInTheDocument()

  await wait(() => expect(/* Here you check that the people is on the page */).toBeInTheDocument())

  // We also check that the API gets called
  expect(getPeople).toHaveBeenCalledOnce()
  expect(getPeople).toHaveBeenCalledWith()
})

As you can see, I'm not checking what's the internal state of App. Instead, I'm checking that a loading spinner is shown and that after that the people appear on the screen and that the API gets called.

This test is more reliable because you're testing what a user would see and not the implementation details.

这篇关于使用 react-testing-library 进行测试时可以手动触发状态更改吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆