【送红宝书】JavaScript 测试系列实战(四):掌握 React Hooks 测试技巧

2020年9月9日 264点热度 0人点赞 0条评论
「为了回馈图雀社区的读者,图雀酱特地挑选了几本书籍送给大家,文末有送书活动详情哦~」

React Hooks 作为复用共同业务逻辑的强大工具,已经在开源库和业务代码中得到了广泛的使用。但是如果一个钩子没有完善的测试覆盖,我们就很难有信心去使用或者分享它。在这篇文章中,我们将体验强大的 react-hooks-testing-library,学习如何去测试钩子的同步和异步逻辑,并最终通过一个完整的例子去了解如何结合 Redux 框架进行测试。

开始使用 react-hooks-testing-library

在上一篇教程中,我们手工编写了非常原始的 React Hooks 测试代码。所幸,由于测试 React Hooks 的需求非常普遍,因此就有了测试 Hooks 的神器:react-hooks-testing-library。它提供了一系列专门用于测试 Hook 的工具函数,能够模拟在真实组件中使用 Hooks。

提示

如果你不熟悉 React Hooks 相关的知识,推荐先学习我们的 React Hooks 相关实战教程。

让我们先安装 react-hooks-testing-library:

npm install @testing-library/react-hooks

react-hooks-testing-library 中最重要的工具之一就是 renderHook 函数,它的工作方式与我们之前创建的 testHook 函数类似。它的参数是至少调用一个 Hook 的回调函数,返回值是一个对象,其中我们需要关心的是其中的 result 属性。result 属性又包含两个属性:

  • current:所测试 Hook 的返回值
  • error:所测试 Hook 抛出的错误(如果有的话)

让我们来结合实际的例子看一下。在之前 useModalManagement 钩子的测试代码中,我们仅仅只测试了调用 Hook 时不会报错。实际上,我们还希望测试以下用例:

  • 默认渲染一个关闭的模态框
  • 当调用 openModal 函数时,能够打开模态框

我们来看看新的测试代码:

// src/useModalManagement.test.js
import useModalManagement from './useModalManagement';
import { renderHook, act } from '@testing-library/react-hooks';

describe('The useModalManagement hook', () => {
  it('should not throw an error', () => {
    renderHook(() => useModalManagement());
  });

  it('should describe a closed modal by default', () => {
    const { result } = renderHook(() => useModalManagement());
    expect(result.current.isModalOpened).toBe(false);
  });

  describe('when the openModal function is called', () => {
    it('should describe an opened modal', () => {
      const { result } = renderHook(() => useModalManagement());
      act(() => {
        result.current.openModal();
      });
      expect(result.current.isModalOpened).toBe(true);
    });
  });
});

内容有点多,我们来逐个用例讲解:

  1. 测试 Hook 不会报错:我们将原来的 testHook 函数改成 react-hooks-testing-library 的 renderHook 函数,这个函数接受的参数是一个调用 Hook 的函数
  2. 测试模态框默认关闭:还是通过 renderHook 渲染 Hook,然后获取到之前提到的 result 对象,进一步通过 result.current.isModalOpened 来获取到模态框的状态,然后用断言语句测试这个状态是 false(关闭状态)
  3. 测试打开模态框:这个测试的难点在于怎么去触发 openModal ,所幸 react-hooks-testing-library 提供了 act 工具函数来模拟浏览器中 Hook 的工作方式;act 函数同样接受一个函数执行一系列同步操作

注意

如果不使用 act 函数,而是直接将操作写在用例中,Jest 会抛出警告,并且可能会遇到一些棘手的边界情况。

通过 npm test 运行测试,全部通过!由于我们丰富了测试用例,对 useModalManagement 钩子的信心也大增!

测试异步钩子

刚才的 useModalManagement 涉及到的都是同步操作,然而在实际应用中,很多钩子都涉及到异步操作,例如 API 数据获取等。那么我们该怎么测试这些异步钩子呢?

实际上,刚才我们用到了 renderHook 的一个重要返回对象 result ,它实际上还提供了 waitForNextUpdate 函数。这个函数调用后会返回 Promise,这个 Promise 在下次渲染 Hook 时进入 Resolve 状态,非常适合用来测试异步更新的逻辑。

提示

react-hooks-testing-library 还提供了一些工具函数用来辅助异步钩子的测试,可参考官方文档的 Async Utilities 部分。

编写一个异步钩子

首先,让我们来写一个简单的异步钩子 useCommentsManagement ,用于从一个公共 API 获取一些评论数据,代码如下:

// src/useCommentsManagement.js
import { useState } from 'react';

function useCommentsManagement({
  const [comments, setComments] = useState([]);

  function fetchComments({
    return fetch('https://jsonplaceholder.typicode.com/comments')
      .then((response) => response.json())
      .then((data) => {
        setComments(data);
      });
  }

  return {
    comments,
    fetchComments,
  };
}

export default useCommentsManagement;

编写测试代码

然后我们来编写 useCommentsManagement 的测试代码如下:

// src/useCommentsManagement.test.js
import { renderHook, act } from '@testing-library/react-hooks';
import useCommentsManagement from './useCommentsManagement';

describe('The useCommentsManagement hook', () => {
  describe('when the fetchComments function is called', () => {
    it('should update the state after a successful request'async () => {
      const { result, waitForNextUpdate } = renderHook(() => useCommentsManagement());

      act(() => {
        result.current.fetchComments();
      });
      await waitForNextUpdate();

      return expect(result.current.comments.length).not.toBe(0);
    });
  });
});

act 函数中触发 fetchComments 拉取评论后,我们调用 waitForNextUpdate 并去 await 它返回的 Promise,当重渲染完成后,就可以使用调用断言语句来进行判断啦。这里我们还是通过 result.current 来获取评论数量。

注意

在编写 Jest 异步测试用例时,如果涉及到 Promise 的使用(包括 async/await ),要确保 return 一个值,否则测试会超时。详细介绍请参考 Jest 异步测试文档。

继续 npm test,一路绿灯!

提示

你也许还记得前面的课程中,我们讲到了如何用 Jest Mock 去避免发起真正的 HTTP 请求,从而能够保证测试不会因为网络问题而挂掉。至于怎么用 Mock 来写,就留给作业给你吧~

测试 Redux + Hooks

在规模较大的应用中,我们通常会使用一个状态管理库来解决复杂的数据流问题,而最受欢迎的选择无疑是 Redux。在这一节中,我们将手把手带你搭建一个完整的 Redux 模型,并且为之编写测试。

提示

这篇文章的重心不是 Redux,因此不会花太多的笔墨在这上面。如果不熟悉或者想复习一下的话,推荐阅读图雀社区的《Redux 包教包会》系列教程。

让我们先安装一下相关的依赖:

npm install redux react-redux

三件套:Action、Reducer 和 Store

之前的模态框钩子 useModalManagement 在内部维护了 isOpened 状态,这里我们将这个状态放到 Redux 中来进行管理。

首先定义相关的 Actions,创建 src/actions/modal.js ,代码如下:

// src/actions/modal.js
const OPEN_MODAL = 'OPEN_MODAL';
const CLOSE_MODAL = 'CLOSE_MODAL';

function openModal({
  return {
    type: OPEN_MODAL,
  };
}

function closeModal({
  return {
    type: CLOSE_MODAL,
  };
}

export { OPEN_MODAL, CLOSE_MODAL, openModal, closeModal };

然后是相关的 Reducer,代码如下:

// src/reducers/modal.js
import { OPEN_MODAL, CLOSE_MODAL } from '../actions/modal';

const initialState = {
  isOpenedfalse,
};

export default function modal(state = initialState, action{
  if (action.type == OPEN_MODAL) {
    return { isOpenedtrue };
  } else if (action.type == CLOSE_MODAL) {
    return { isOpenedfalse };
  } else {
    return state;
  }
}

我们通过 combineReducers 将所有的 Reducer 结合成 rootReducer(虽然这里只有一个 Reducer,但是这里为了完整地演示):

// src/reducers/index.js
import { combineReducers } from 'redux';
import modal from './modal';

const rootReducer = combineReducers({ modal });

export default rootReducer;

最后则是 Store,代码如下:

// src/store.js
import { createStore } from 'redux';
import rootReducer from './reducers';

const store = createStore(rootReducer);

export default store;

用 Redux 重写 useModalManagement

由于接入了 Redux,我们对之前的 useCommentsManagement 要来进行一波大刀阔斧的修改。修改后的代码如下:

// src/useModalManagement.js
import { useDispatch, useSelector } from 'react-redux';
import * as modalActions from './actions/modal';

function useModalManagement({
  const isModalOpened = useSelector((state) => state.modal.isOpened);
  const dispatch = useDispatch();

  function openModal({
    dispatch(modalActions.openModal());
  }

  function closeModal({
    dispatch(modalActions.closeModal());
  }

  return {
    isModalOpened,
    openModal,
    closeModal,
  };
}

export default useModalManagement;

这里我们使用 react-redux 提供的 useSelectoruseDispatch 钩子来分别获取状态和派发函数。

OK,让我们把测试跑起来……居然报错了,主要的提示信息如下:

Invariant Violation: could not find react-redux context value; please ensure the component is wrapped in a <Provider>

含义很明确,我们没有提供 Redux 上下文。如果你熟悉 Redux 的话,你应该记得 react-redux 提供了 Provider 组件来向所有子组件提供 Store 对象,但是在测试的时候,我们该怎么让 Provider 去包裹待测试的钩子呢?

通过 wrapper 来提供上下文

幸运的是,renderHook 支持传入第二个参数,用于调节钩子的渲染配置,其中一个我们需要的配置就是 wrapper 。这个 wrapper 专门用来提供 React Context,当然也适用于 Redux 的 Provider

修改 useCommentsManagement 的测试代码如下:

// src/useModalManagement.test.js
import React from 'react';
import useModalManagement from './useModalManagement';
import { renderHook, act } from '@testing-library/react-hooks';
import { Provider } from 'react-redux';
import store from './store';

describe('The useModalManagement hook', () => {
  // ...

  it('should describe a closed modal by default', () => {
    const { result } = renderHook(() => useModalManagement(), {
      wrapper({ children }) => <Provider store={store}>{children}</Provider>,
    });
    expect(result.current.isModalOpened).toBe(false);
  });

  describe('when the openModal function is called', () => {
    it('should describe an opened modal', () => {
      const { result } = renderHook(() => useModalManagement(), {
        wrapper({ children }) => <Provider store={store}>{children}</Provider>,
      });
      act(() => {
        result.current.openModal();
      });
      expect(result.current.isModalOpened).toBe(true);
    });
  });
});

再次运行测试,又全部通过了!

小结

在这篇文章中,我们体验了强大的 react-hooks-testing-library,先后测试了同步和异步的钩子,最后还结合 Redux 来测了一波。在下一篇教程中,我们终于要接触激动人心的端到端(E2E)测试了,敬请期待吧!

送红宝书

人的一生中总要读几本经典书,在这个“经典”泛滥的年代,什么才是权威的代表,我想大概是一本的书的口碑,能积累下上佳口碑的书,往往也是能经得住时间推敲的。比如这本:

图片我相信所有前端开发者的案头都有这样一本书。这本书最早的版本第 2 版可以追溯到 2006 年,距离现在有 14 年之久了,而这本书依旧保持着生命力,影响着更多的前端人。
这本书前前后后帮助几代前端人入门,给大家留下不可磨灭的记忆,它除了是工具书中的翘楚,也是前端发展史的见证者。所以,很多程序员亲切地称它为JavaScript “红宝书”。
当然也有一些初学者,听到“高级”二字就被吓退了,觉得这很难并不适合新手。但是其实这本书适用面很广,不管你是新手还是高手,始终能从书中得到启发和收获,也能让你做到常读常新。
一本书能长销 10 多年,离不开优秀的作者团队提供了优质的内容,更重要的是每一版都在不断优化知识框架的设置,力求让更多入门者有更好的学习体验。
目前,这本书第 1 版的作者已经去世。第 2 版,还有我们熟知的第 3 版的作者 Nicholas C.Zakas 如今也因身体原因退出了第 4 版的写作,就像作者在 Twitter 上说的一样:“《JavaScript高级程序设计》这本书这好像是一场每日秀,如今它迎来了第 3 任主持人。”
而新版作者 Matt Frisbie 将带着更好的内容出发!
图片(第3版作者Twitter截图)
先来介绍下,第 4 版的作者马特·弗里斯比(Matt Frisbie),目前担任 Gosellout 公司的 CTO,曾担任谷歌公司软件工程师,精通前端技术,拥有十余年 Web 开发经验,除本书外另著有 AngularJS 等前端主题图书。毕业于伊利诺伊大学厄巴纳-尚佩恩分校,是一位经验十足的前端。
以下是第 4 版的封面,学了这么多年,书封上拿望远镜的小孩终于站起来了,这是不是也预示着我们距离精通 JavaScript 又更近一步。
图片
很多人更关注新版会带来哪些更新?
这一版仍旧延续上一版的框架和格局,删减了已经过时的内容,在此基础上又翔实地增补了 ES2015 到 ES2019 的全新内容,作者详尽讨论了 JavaScript 的各个方面,从 JavaScript 的起源开始,逐步讲解到新出现的技术,其中重点介绍 ECMAScript 和 DOM 标准。
新版涵盖了 ECMAScript 2019 ,全面、深入地介绍了 JavaScript 开发者必须掌握的前端开发技术,涉及 JavaScript 的基础特性和高级特性。还同时介绍了近几年来涌现的重要新规范,包括 Fetch API、模块、工作者线程、服务线程以及大量新 API。
我相信新版本一定会不负众望,值得等待。
第 4 版差不多有 900 页,为了保证这本书能准时上市,这中间离不开译者和编辑老师的通力配合。几乎每周,大家都会坐下来沟通一下这本书的进度。
在本书正式印刷之前,译者李松峰老师更是邀请了众多前端大佬,帮忙审校,确保内容万无一失。
大家都知道,工作一天利用下班时间来翻译这本巨著,是需要超凡的耐力的。平时,我们看一本 900 页的书都未必能坚持住,更何况是翻译。但是李松峰老师做到了,这样一件极其枯燥乏味的事情,李松峰老师却把它做到了极致,这也不得不让我们佩服。


图片

JavaScript经典图书
第3版豆瓣评分9.3

《JavaScript高级程序设计(第4版)》
[美]马特·弗里斯比 著
李松峰 译

本书是 JavaScript 经典图书的新版。第 4 版涵盖 ECMAScript 2019,全面、深入地介绍了 JavaScript 开发者必须掌握的前端开发技术,涉及 JavaScript 的基础特性和高级特性。书中详尽讨论了 JavaScript 的各个方面,从 JavaScript 的起源开始,逐步讲解到新出现的技术,其中重点介绍 ECMAScript 和 DOM 标准。

在此基础上,接下来的各章揭示了 JavaScript 的基本概念,包括类、期约、迭代器、代理,等等。另外,书中深入探讨了客户端检测、事件、动画、表单、错误处理及 JSON。本书同时也介绍了近几年来涌现的重要新规范,包括 Fetch API、模块、工作者线程、服务线程以及大量新 API。



福利时间

关注公众号,在后台回复 “送书”,参与抽奖,有 2 本《JavaScript 高级程序设计》免费送哦~

PS:邀请好友参与抽奖可以提高中奖的概率,对这本书有兴趣的同学可以邀请好友参加哦~

图片

开奖时间

2020-09-12 20:00 


最后

感谢图灵教育对本次活动的支持。
未中奖的小伙伴,可以关注图灵教育的红宝书活动:
https://www.ituring.com.cn/book/2472

更多优质图书:https://www.ituring.com.cn/

12430【送红宝书】JavaScript 测试系列实战(四):掌握 React Hooks 测试技巧

root

这个人很懒,什么都没留下

文章评论