首页 文章

使用Jest&Enzyme进行异步提取后测试组件的状态(使用模块)

提问于
浏览
1

我正在尝试用Jest测试React组件的状态 . 在ComponentDidUpdate上,如果有人登录并使用用户名更新state.user,它将在服务器上获取用户名 . 如果没有用户登录,则会将state.pleaseLogin切换为true .

import React from "react";
import "./Header.css";
import "whatwg-fetch";
import LoggedInNav from "./LoggedInNav";
import LoggedOutNav from "./LoggedOutNav";
import urls from "../../urls";
import getUser from "../fetch/getUser";

export default class Header extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      user: null,
      pleaseLogin: false
    };
    this.handleGetUser = this.handleGetUser.bind(this);
  }

  handleGetUser() {
    getUser().then(res => {
      if (res.hasOwnProperty("username")) {
        this.setState({
          user: res.username
        });
      } else {
        this.setState({
          pleaseLogin: true
        });
      }
    });
  }
  componentDidMount() {
    this.handleGetUser();
  }

  render() {
    let nav;
    if (this.state.user) {
      nav = <LoggedInNav user={this.state.user} />;
    } else if (this.state.pleaseLogin) {
      nav = <LoggedOutNav />;
    } else {
      nav = null;
    }
    return (
      <header className="app-header">
        <h1 className="header-brand">Watchers</h1>
        {nav}
      </header>
    );
  }
}

以下是getUser()的代码,用于在服务器上获取用户名:

export default () => {
  return fetch(`${BASE_URL}/api/user/user_data`, {
    credentials: "include"
  })
    .then(res => res.json())
    .catch(error => {
      console.log(error);
      return error;
    });
};

我手动模拟了API调用 . 该文件名为'getUser.js',位于原始'getUser.js'旁边的 mocks 文件夹中,如下所示:

export default () => {
  return Promise.resolve({
    username: 'Username'
  })
}

然后测试:

import React, { Component } from "react";
import Enzyme, { shallow, mount } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import Header from "../react/components/Header";
import getUser from "../react/fetch/getUser";

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

jest.mock("../react/fetch/getUser");
const wrapper = mount(<Header />);

describe("<Header/>", () => {
  describe("component class", () => {
    it("should update state.user after componentDidMount()", () => {
      return getUser().then(() => {
        expect(wrapper.state()).toEqual({
          user: "Username",
          pleaseLogin: false
        });
      });
    });
  });
});

测试通过,状态发生了变化,state.user现在等于“Username” . 如果我更改我的模拟模块并使其返回一个空对象,它也可以正常工作,并且state.pleaseLogin切换为true . 但是我必须手动测试这两种情况 . 我尝试使用jest.mock('... module',()=> {})直接在我的测试文件中模拟这两个不同的实现,并使用jest.doMock('... module',()=> {})但是当我运行测试时状态不会更新;虽然它与手动模拟 . 我不确定问题的来源,也许这与异步性或在Jest中嘲笑有关?

1 回答

  • 1

    我找到了两种不同的测试方法 .

    由于模拟模块仅包含一个函数,因此手动模拟可能不是最佳解决方案 . 不过这是我写测试的方式:

    1.在 mocks 文件夹中手动模拟

    // In the example, the function getUser is exported with 'export default'.
    module.exports = jest.fn(() => Promise.resolve({}))
    
    
    // If the function is exported with 'export const getUser = () => {}':
    module.exports = {
        getUser: jest.fn(() => Promise.resolve({}))
    }
    

    Test

    jest.mock("../react/fetch/getUser");
    const getUser = require("../react/fetch/getUser");
    
    // If function exported with 'export const getUser = () => {}':
    // const {getUser} = require("../react/fetch/getUser");
    
    describe("<Header/>", () => {
      it("should update state.user after componentDidMount()", () => {
    
        getUser.mockImplementation(() =>
          Promise.resolve({ username: "Username" })
        );
    
        const wrapper = shallow(<Header />);
    
        expect.assertions(1);
    
        return (
          getUser()
            .then(() => {
              expect(wrapper.state()).toEqual({
                user: "Username",
                pleaseLogin: false
              });
            })
        );
      });
    
      it("should update state.pleaseLogin after componentDidMount()", () => {
        getUser.mockImplementation(() => Promise.resolve({}));
        const wrapper = mount(<Header />);
        return (
          getUser()
            .then(() => {
              expect(wrapper.state()).toEqual({
                user: null,
                pleaseLogin: true
              });
            })
        );
      });
    });
    

    2.创建 mock function 以在测试代码中使用

    Test

    // If function exported with 'export default'
    jest.mock("../react/fetch/getUser", () => jest.fn());
    const getUser = require("../react/fetch/getUser");
    
    // Else
    // jest.mock("../react/fetch/getUser", () => ({getUser: jest.fn()}));
    // const {getUser} = require("../react/fetch/getUser")
    
    describe("<Header/>", () => {
      it("should update state.user after componentDidMount()", () => {
        const promise = Promise.resolve({
          username: "Username"
        });
        getUser.mockImplementation(() => promise);
    
        const wrapper = shallow(<Header />);
    
        expect.assertions(1);
        return getUser().then(() => {
          expect(wrapper.state()).toEqual({
            user: "Username",
            pleaseLogin: false
          });
        });
      });
    });
    

    一些值得注意的事情:

    • 模拟内部测试

    我试图在测试中使用mock和doMock失败 . 它可能与此问题有关:https://github.com/facebook/jest/issues/2582

    • 模拟实现

    当您需要定义从另一个模块创建的模拟函数的默认实现时,mockImplementation方法很有用

    但是,mockImplementationOnce()将在此测试中引发错误

    • 酶浅()/ mount()

    必须在模拟之后调用酶shallow()或mount()才能使用该实现 . 否则我们得到'TypeError:无法读取属性'然后'未定义'

    • 返回承诺

    承诺需要退回 . 如果没有'return',那么'then'中的期望将被跳过,因为我们不等待异步调用返回 .

    Jest Docs:“一定要返回承诺 - 如果省略这个return语句,你的测试将在fetchData完成之前完成 . ”

    您可以查看以下链接,了解如果省略return,会发生什么:https://github.com/facebook/jest/issues/1924

相关问题