您好,登录后才能下订单哦!
本篇文章为大家展示了JavaScript中如何使用Mock模拟模块并处理组件交互,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。
我们将学习如何测试更复杂的组件,包括用 Mock 去编写涉及外部 API 的测试,以及通过 Enzyme 来轻松模拟组件交互
我们的应用程序通常需要从外部的 API 获取数据。在编写测试时,外部 API 可能由于各种原因而失败。我们希望我们的测试是可靠和独立的,而最常见的解决方案就是 Mock。
首先让我们改造组件,使其能够通过 API 获取数据。安装 axios:
npm install axios
 
  然后改写 TodoList 组件如下:
// src/TodoList.js
import React, { Component } from 'react';
import axios from 'axios';
import Task from './Task';
const apiUrl = 'https://api.tuture.co';
class ToDoList extends Component {
  state = {
    tasks: [],
  };
  componentDidMount() {
    return axios
      .get(`${apiUrl}/tasks`)
      .then((tasksResponse) => {
        this.setState({ tasks: tasksResponse.data });
      })
      .catch((error) => console.log(error));
  }
  render() {
    return (
      <ul>
        {this.state.tasks.map((task) => (
          <Task key={task.id} id={task.id} name={task.name} />
        ))}
      </ul>
    );
  }
}
export default ToDoList;
 
  TodoList 被改造成了一个“聪明组件”,在 componentDidMount 生命周期函数中通过 axios 模块异步获取数据。
Jest 支持对整个模块进行 Mock,使得组件不会调用原始的模块,而是调用我们预设的 Mock 模块。按照官方推荐,我们创建 mocks 目录并把 mock 文件放到其中。创建 axios 的 Mock 文件 axios.js,代码如下:
// src/__mocks__/axios.js
'use strict';
module.exports = {
  get: () => {
    return Promise.resolve({
      data: [
        {
          id: 0,
          name: 'Wash the dishes',
        },
        {
          id: 1,
          name: 'Make the bed',
        },
      ],
    });
  },
};
 
  这里的 axios 模块提供了一个 get 函数,并且会返回一个 Promise,包含预先设定的假数据。
让我们开始 Mock 起来!打开 TodoList 的测试文件,首先在最前面通过 jest.mock 配置 axios 模块的 Mock(确保要在 import TodoList 之前),在 Mock 之后,无论在测试还是组件中使用的都将是 Mock 版本的 axios。然后创建一个测试用例,检查 Mock 模块是否被正确调用。代码如下:
// src/TodoList.test.js
import React from 'react';
import { shallow, mount } from 'enzyme';
import axios from 'axios';
jest.mock('axios');
import ToDoList from './ToDoList';
describe('ToDoList component', () => {
  // ...
  describe('when rendered', () => {
    it('should fetch a list of tasks', () => {
      const getSpy = jest.spyOn(axios, 'get');
      const toDoListInstance = shallow(<ToDoList />);
      expect(getSpy).toBeCalled();
    });
  });
});
 
  测试模块中一个函数是否被调用实际上是比较困难的,但是所幸 Jest 为我们提供了完整的支持。首先通过 jest.spyOn,我们便可以监听一个函数的使用情况,然后使用配套的 toBeCalled Matcher 来判断该函数是否被调用。整体代码十分简洁,同时也保持了很好的可读性。
如果你忘记了 Jest Matcher 的含义,推荐阅读本系列的第一篇教程。
一个实际的项目总会不断迭代,当然也包括我们的 TodoList 组件。对于一个待办事项应用来说,最重要的当然便是添加新的待办事项。
修改 TodoList 组件,代码如下:
// src/TodoList.js
// ...
class ToDoList extends Component {
  state = {
    tasks: [],
    newTask: '',
  };
  componentDidMount() {
    // ...
      .catch((error) => console.log(error));
  }
  addATask = () => {
    const { newTask, tasks } = this.state;
    if (newTask) {
      return axios
        .post(`${apiUrl}/tasks`, { task: newTask })
        .then((taskResponse) => {
          const newTasksArray = [...tasks];
          newTasksArray.push(taskResponse.data.task);
          this.setState({ tasks: newTasksArray, newTask: '' });
        })
        .catch((error) => console.log(error));
    }
  };
  handleInputChange = (event) => {
    this.setState({ newTask: event.target.value });
  };
  render() {
    const { newTask } = this.state;
    return (
      <div>
        <h2>ToDoList</h2>
        <input onChange={this.handleInputChange} value={newTask} />
        <button onClick={this.addATask}>Add a task</button>
        <ul>
          {this.state.tasks.map((task) => (
            <Task key={task.id} id={task.id} name={task.name} />
          ))}
        </ul>
      </div>
    );
  }
}
export default ToDoList;
 
  由于我们大幅改动了 TodoList 组件,我们需要更新快照:
npm test -- -u
 
  如果你不熟悉 Jest 快照测试,请回看本系列第二篇教程。
更新后的快照文件反映了我们刚刚做的变化:
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ToDoList component when provided with an array of tasks should render correctly 1`] = `
<div>
  <h2>
    ToDoList
  </h2>
  <input
    onChange={[Function]}
    value=""
  />
  <button
    onClick={[Function]}
  >
    Add a task
  </button>
  <ul />
</div>
`;
 
   
  在上面迭代的 TodoList 中,我们使用了 axios.post。这意味着我们需要扩展 axios 的 mock 文件:
// src/__mocks__/axios.js
'use strict';
let currentId = 2;
module.exports = {
  get: () => {
    return Promise.resolve({
      // ...
      ],
    });
  },
  post: (url, data) => {
    return Promise.resolve({
      data: {
        task: {
          name: data.task,
          id: currentId++,
        },
      },
    });
  },
};
 
  可以看到上面,我们添加了一个
currentId变量,因为我们需要保持每个 task 的唯一性。
让我们开始测试吧!我们测试的第一件事是检查修改输入值是否更改了我们的状态:
我们修改 app/components/TodoList.test.js 如下:
import React from 'react';
import { shallow } from 'enzyme';
import ToDoList from './ToDoList';
describe('ToDoList component', () => {
  describe('when the value of its input is changed', () => {
    it('its state should be changed', () => {
      const toDoListInstance = shallow(
        <ToDoList/>
      );
      const newTask = 'new task name';
      const taskInput = toDoListInstance.find('input');
      taskInput.simulate('change', { target: { value: newTask }});
      expect(toDoListInstance.state().newTask).toEqual(newTask);
    });
  });
});
 
  这里要重点指出的就是 simulate[1] 函数的调用。这是我们几次提到的ShallowWrapper的功能。我们用它来模拟事件。它第一个参数是事件的类型(由于我们在输入中使用onChange,因此我们应该在此处使用change),第二个参数是模拟事件对象(event)。
为了进一步说明问题,让我们测试一下用户单击按钮后是否从我们的组件发送了实际的 post 请求。我们修改测试代码如下:
import React from 'react';
import { shallow } from 'enzyme';
import ToDoList from './ToDoList';
import axios from 'axios';
jest.mock('axios');
describe('ToDoList component', () => {
  describe('when the button is clicked with the input filled out', () => {
    it('a post request should be made', () => {
      const toDoListInstance = shallow(
        <ToDoList/>
      );
      const postSpy = jest.spyOn(axios, 'post');
      const newTask = 'new task name';
      const taskInput = toDoListInstance.find('input');
      taskInput.simulate('change', { target: { value: newTask }});
      const button = toDoListInstance.find('button');
      button.simulate('click');
      expect(postSpy).toBeCalled();
    });
  });
});
 
  感谢我们的 mock 和 simulate 事件,测试通过了!现在事情会变得有些棘手。我们将测试状态是否随着我们的新任务而更新,其中比较有趣的是请求是异步的,我们继续修改代码如下:
import React from 'react';
import { shallow } from 'enzyme';
import ToDoList from './ToDoList';
import axios from 'axios';
jest.mock('axios');
describe('ToDoList component', () => {
  describe('when the button is clicked with the input filled out, the new task should be added to the state', () => {
    it('a post request should be made', () => {
      const toDoListInstance = shallow(
        <ToDoList/>
      );
      const postSpy = jest.spyOn(axios, 'post');
      const newTask = 'new task name';
      const taskInput = toDoListInstance.find('input');
      taskInput.simulate('change', { target: { value: newTask }});
      const button = toDoListInstance.find('button');
      button.simulate('click');
      const postPromise = postSpy.mock.results.pop().value;
      return postPromise.then((postResponse) => {
        const currentState = toDoListInstance.state();
        expect(currentState.tasks.includes((postResponse.data.task))).toBe(true);
      })
    });
  });
});
 
  就像上面看到的,postSpy.mock.results 是 post 函数发送结果的数组,通过使用它,我们可以得到返回的 promise,我们可以从 value 属性中取到这个 promise。从测试返回 promise 是确保 Jest 等待其异步方法执行结束的一种方法。
在本文中,我们介绍了 mock 模块,并将其用于伪造API调用。由于没有发起实际的 post 请求,我们的测试可以更可靠,更快。除此之外,我们还在整个 React 组件中模拟了事件。我们检查了它是否产生了预期的结果,例如组件的请求或状态变化。为此,我们了解了 spy 的概念。
Hooks 是 React 的一个令人兴奋的补充,毫无疑问,它可以帮助我们将逻辑与模板分离。这样做使上述逻辑更具可测试性。不幸的是,测试钩子并没有那么简单。在本文中,我们研究了如何使用 react-hooks-testing-library[2] 处理它。
我们创建 src/useModalManagement.js 文件如下:
// src/useModalManagement.js
import { useState } from 'react';
function useModalManagement() {
  const [isModalOpened, setModalVisibility] = useState(false);
  function openModal() {
    setModalVisibility(true);
  }
  function closeModal() {
    setModalVisibility(false);
  }
  return {
    isModalOpened,
    openModal,
    closeModal,
  };
}
export default useModalManagement;
 
  上面的 Hooks 可以轻松地管理模式状态。让我们开始测试它是否不会引发任何错误,我们创建 useModalManagement.test.js
// src/useModalManagement.test.js
import useModalManagement from './useModalManagement';
describe('The useModalManagement hook', () => {
  it('should not throw an error', () => {
    useModalManagement();
  });
});
 
  我们运行测试,得到如下的结果:
FAIL useModalManagement.test.js
  The useModalManagement hook
    ✕ should not throw an error按 ⌘+↩ 退出
 
  不幸的是,上述测试无法正常进行。我们可以通过阅读错误消息找出原因:
无效的 Hooks 调用, Hooks 只能在函数式组件的函数体内部调用。
React文档[3] 里面提到:我们只能从函数式组件或其他 Hooks 中调用 Hooks。我们可以使用本系列前面部分介绍的 enzyme 库来解决此问题,而且使了一点小聪明,我们创建 testHook.js :
// src/testHook.js
import React from 'react';
import { shallow } from 'enzyme';
function testHook(hook) {
  let output;
  function HookWrapper() {
    output = hook();
    return <></>;
  }
  shallow(<HookWrapper />);
  return output;
}
export default testHook;
 
  我们继续迭代 useModalManagement.test.js,修改内容如下:
// src/useModalManagement.test.js
import useModalManagement from './useModalManagement';
import testHook from './testHook';
describe('The useModalManagement hook', () => {
  it('should not throw an error', () => {
    testHook(useModalManagement);
  });
});
 
  我们允许测试,得到如下结果:
PASS useModalManagement.test.js
  The useModalManagement hook
    ✓ should not throw an error
 
  好多了!但是,上述解决方案不是很好,并且不能为我们提供进一步测试 Hooks 的舒适方法。这就是我们使用 react-hooks-testing-library[4] 的原因。
上述内容就是JavaScript中如何使用Mock模拟模块并处理组件交互,你们学到知识或技能了吗?如果还想学到更多技能或者丰富自己的知识储备,欢迎关注亿速云行业资讯频道。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。