在 Jest 测试中触发 useEffect [英] Trigger useEffect in Jest testing

查看:25
本文介绍了在 Jest 测试中触发 useEffect的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 Jest 和 Enzyme 来测试 React 功能组件.

I'm using Jest and Enzyme to test a React functional component.

我的组件:

export const getGroups = async () => {
    const data = await fetch(groupApiUrl);
    return await data.json()
};

export default function MyWidget({
  groupId,
}) {
  // Store group object in state
  const [group, setGroup] = useState(null);

  // Retrive groups on load
  useEffect(() => {
    if (groupId && group === null) {
      const runEffect = async () => {
        const  { groups  } = await getGroups();
        const groupData = groups.find(
          g => g.name === groupId || g.id === Number(groupId)
        );

        setGroup(groupData);
      };
      runEffect();
    }
  }, [group, groupId]);

  const params =
    group && `&id=${group.id}&name=${group.name}`;
  const src = `https://mylink.com?${params ? params : ''}`;

  return (
    <iframe src={src}></iframe>
  );
}

当我写这个测试时:

  it('handles groupId and api call ', () => {
    // the effect will get called
    // the effect will call getGroups
    // the iframe will contain group parameters for the given groupId


   act(()=> {
        const wrapper = shallow(<MyWidget surface={`${USAGE_SURFACES.metrics}`} groupId={1} />) 
        console.log(wrapper.find("iframe").prop('src'))
    })
   })

返回的src在url中不包含组信息.我如何触发 useEffect 以及其中的所有内容?

The returned src doesn't contain the group information in the url. How do I trigger useEffect and and everything inside that?

我学到的一件事是shallow 不会触发useEffect.我仍然没有得到正确的 src 但我已经切换到 mount 而不是 shallow

One thing I learned is the shallow will not trigger useEffect. I'm still not getting the correct src but I've switched to mount instead of shallow

推荐答案

这是一个模拟 fetch 的最小完整示例.您的组件几乎可以归结为通用的 fire-fetch-and-set-state-with-response-data 习惯用法:

Here's a minimal, complete example of mocking fetch. Your component pretty much boils down to the generic fire-fetch-and-set-state-with-response-data idiom:

import React, {useEffect, useState} from "react";

export default function Users() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    (async () => {
      const res = await fetch("https://jsonplaceholder.typicode.com/users");
      setUsers(await res.json());
    })();
  }, []);

  return <p>there are {users.length} users</p>;
};

随意在浏览器中运行这个组件:

Feel free to run this component in the browser:

<script type="text/babel" defer>
const {useState, useEffect} = React;

const Users = () => {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    (async () => {
      const res = await fetch("https://jsonplaceholder.typicode.com/users");
      setUsers(await res.json());
    })();
  }, []);

  return <p>there are {users.length} users</p>;
};

ReactDOM.render(<Users />, document.body);
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>

您可以看到组件最初呈现的值为 0,然后当请求到达时,所有 10 个用户对象都处于状态,并触发第二次呈现以显示更新的文本.

You can see the component initially renders a value of 0, then when the request arrives, all 10 user objects are in state and a second render is triggered showing the updated text.

让我们编写一个幼稚(但不正确)的单元测试,mocking fetch 因为它在 Node 中不存在:

Let's write a naive (but incorrect) unit test, mocking fetch since it doesn't exist in Node:

import {act} from "react-dom/test-utils";
import React from "react";
import Enzyme, {mount} from "enzyme";
import Adapter from "enzyme-adapter-react-16";

import Users from "../src/Users";

Enzyme.configure({adapter: new Adapter()});

describe("Users", () => {
  let wrapper;
  let users;
  
  beforeEach(() => {
    const mockResponseData = [{id: 1}, {id: 2}, {id: 3}];
    users = mockResponseData.map(e => ({...e}));
    jest.clearAllMocks();
    global.fetch = jest.fn(async () => ({
      json: async () => mockResponseData
    }));
    wrapper = mount(<Users />);
  });
  
  it("renders a count of users", () => {
    const p = wrapper.find("p");
    expect(p.exists()).toBe(true);
    expect(p.text()).toEqual("there are 3 users");
  });
});

一切似乎都很好——我们加载包装器,找到段落并检查文本.但是运行它会给出:

All seems well--we load up the wrapper, find the paragraph and check the text. But running it gives:

Error: expect(received).toEqual(expected) // deep equality

Expected: "there are 3 users"
Received: "there are 0 users"

显然,没有等待承诺并且包装器没有注册更改.当承诺在任务队列中等待时,断言在调用堆栈上同步运行.当 promise 用数据解析时,套件已经结束.

Clearly, the promise isn't being awaited and the wrapper is not registering the change. The assertions run synchronously on the call stack as the promise waits in the task queue. By the time the promise resolves with the data, the suite has ended.

我们想让测试块await下一个tick,也就是在运行之前等待调用栈和pending promise解决.Node 提供 setImmediateprocess.nextTick 用于实现此目的.

We want to get the test block to await the next tick, that is, wait for the call stack and pending promises to resolve before running. Node provides setImmediate or process.nextTick for achieving this.

最后,wrapper.update() 函数启用与 React 组件树的同步,以便我们可以看到更新的 DOM.

Finally, the wrapper.update() function enables synchronization with the React component tree so we can see the updated DOM.

这是最终的工作测试:

import {act} from "react-dom/test-utils";
import React from "react";
import Enzyme, {mount} from "enzyme";
import Adapter from "enzyme-adapter-react-16";

import Users from "../src/Users";

Enzyme.configure({adapter: new Adapter()});

describe("Users", () => {
  let wrapper;
  let users;
  
  beforeEach(() => {
    const mockResponseData = [{id: 1}, {id: 2}, {id: 3}];
    users = mockResponseData.map(e => ({...e}));
    jest.clearAllMocks();
    global.fetch = jest.fn(async () => ({
      json: async () => mockResponseData
    }));
    wrapper = mount(<Users />);
  });
  
  it("renders a count of users", async () => {
    //                           ^^^^^
    await act(() => new Promise(setImmediate)); // <--
    wrapper.update();                           // <--
    const p = wrapper.find("p");
    expect(p.exists()).toBe(true);
    expect(p.text()).toEqual("there are 3 users");
  });
});

new Promise(setImmediate) 技术还可以帮助我们在承诺解决之前对状态进行断言.act(来自 react-dom/test-utils)对于避免 警告是必要的:在测试中对用户的更新没有包含在 act(...)useEffect 一起弹出.

The new Promise(setImmediate) technique also helps us assert on state before the promise resolves. act (from react-dom/test-utils) is necessary to avoid Warning: An update to Users inside a test was not wrapped in act(...) that pops up with useEffect.

在上面的代码中加入这个测试也通过了:

Adding this test to the above code also passes:

it("renders a count of 0 users initially", () => {
  return act(() => {
    const p = wrapper.find("p");
    expect(p.exists()).toBe(true);
    expect(p.text()).toEqual("there are 0 users");
    return new Promise(setImmediate);
  });
});

当使用 setImmediate 时,测试回调是异步的,因此需要返回一个 promise 以确保 Jest 正确等待它.

The test callback is asynchronous when using setImmediate, so returning a promise is necessary to ensure Jest waits for it correctly.

本文使用 Node 12、Jest 26.1.0、Enzyme 3.11.0 和 React 16.13.1.

This post uses Node 12, Jest 26.1.0, Enzyme 3.11.0 and React 16.13.1.

这篇关于在 Jest 测试中触发 useEffect的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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