模拟 API 调用和模拟 React 组件交互

更新日期: 2019-10-19阅读: 2k标签: 测试

今天,我们进一步测试 react 组件。它涉及模拟组件交互和模拟 api 调用。你将学到两种方法,开始吧!


模拟

对于我们的程序来说,从 API 获取一些数据是很常见的。但是它可能由于各种原因而失败,例如 API 被关闭。我们希望测试可靠且独立,并确保可以模拟某些模块。我们把 ToDoList 组件修改为智能组件。

app/components/ToDoList.component.js
import React, { Component } from 'react';
import Task from "../Task/Task";
import axios from 'axios';
 
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 (
      <div>
        <h1>ToDoList</h1>
        <ul>
          {
            this.state.tasks.map(task =>
              <Task key={task.id} id={task.id} name={task.name}/>
            )
          }
        </ul>
      </div>
    )
  }
}
 
export default ToDoList;

它使用 axios 提取数据,所以需要模拟该模块,因为我们不希望发出实际的请求。此类模拟文件在 mocks 目录中定义,在该目录中,文件名被视为模拟模块的名称。

__mocks__/axios.js
'use strict';
module.exports = {
  get: () => {
    return Promise.resolve({
      data: [
        {
          id: 0,
          name: 'Wash the dishes'
        },
        {
          id: 1,
          name: 'Make the bed'
        }
      ]
    });
  }
};
如果你要模拟 Node 的某些核心模块(例如 fs 或 path ),则需要在模拟文件中明确调用 jest.mock('moduleName')

Jest 允许我们对函数进行监视:接下来测试是否调用了我们所创建的 get 函数。

app/components/ToDoList.test.js
import React from 'react';
import { shallow } from 'enzyme';
import ToDoList from './ToDoList';
import axios from 'axios';
 
jest.mock('axios');
 
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.mock('axios'),Jest 在的测试和组件中都用我们的模拟代替了 axios

spyOn 函数返回一个 mock函数。有关其功能的完整列表,请阅读文档。我们的测试检查组件在渲染和运行之后是否从模拟中调用 get函数,并成功执行。

 PASS  app/components/ToDoList/ToDoList.test.js
  ToDoList component
    when rendered
      ✓ should fetch a list of tasks

如果你在多个测试中监视模拟函数,请记住清除每个测试之间的模拟调用,例如通过运行 getSpy.mockClear(),否则函数调用的次数将在测试之间保持不变。你还可以通过在 package.json 文件中添加以下代码段来使其成为默认行为:

"jest": {
  "clearMocks": true
}


模拟获取 API

另一个常见情况是使用 Fetch API。一个窍门是它是附加到 window 对象的全局函数并对其进行模拟,可以将其附加到 global 对象。首先,让我们创建模拟的 fetch 函数。

__mock__/fetch.js
export default function() {
  return Promise.resolve({
    json: () =>
      Promise.resolve([
        {
          id: 0,
          name: 'Wash the dishes'
        },
        {
          id: 1,
          name: 'Make the bed'
        }
      ])
 
  })
}

然后,将其导入 setupTests.js 文件中。

app/setupTests.js
import Adapter from 'enzyme-adapter-react-16';
import { configure } from 'enzyme';
import fetch from './__mocks__/fetch';
 
global.fetch = fetch;
 
configure({adapter: new Adapter()});
注意,你需要在 package.json 中提供指向 setupTests.js 文件的路径——它在本教程的第二部分中进行了介绍。

现在你可以在组件中自由使用 fetch 了。

componentDidMount() {
  return fetch(`${apiUrl}/tasks`)
    .then(tasksResponse => tasksResponse.json())
    .then(tasksData => {
      this.setState({
        tasks: tasksData
      })
    })
    .catch(error => {
      console.log(error);
    })
}

设置监视时,请记住将其设置为 window.fetch

app/components/ToDoList.test.js
describe('ToDoList component', () => {
  describe('when rendered', () => {
    it('should fetch a list of tasks', () => {
      const fetchSpy = jest.spyOn(window, 'fetch');
      const toDoListInstance = shallow(
        <ToDoList/>
      );
      expect(fetchSpy).toBeCalled();
    });
  });
});


模拟 React 组件的交互

在之前的文章中,我们提到了阅读组件的状态或属性,但这是在实际与之交互时。为了说明这一点,我们将增加一个把任务添加到 ToDoList 的功能。

app/components/ToDoList.js
import React, { Component } from 'react';
import Task from "../Task/Task";
import axios from 'axios';
 
class ToDoList extends Component {
  state = {
    tasks: [],
    newTask: '',
  }
  componentDidMount() {
    return axios.get(`${apiUrl}/tasks`)
      .then(taskResponse => {
        this.setState({
          tasks: taskResponse.data
        })
      })
      .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>
        <h1>ToDoList</h1>
        <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;

如你所见,我们在此处使用了 axios.post。这意味着我们需要扩展 axios 模拟。

__mocks__/axios.js
'use strict';
 
let currentId = 2;
 
module.exports = {
  get: (url) =&gt; {
    return Promise.resolve({
      data: [
        {
          id: 0,
          name: 'Wash the dishes'
        },
        {
          id: 1,
          name: 'Make the bed'
        }
      ]
    });
  },
  post: (url, data) {
    return Promise.resolve({
      data: {
        task: {
          name: data.task,
          id: currentId++
        }
      }
    });
  }
};
我介绍 currentId 变量的原因是想保持ID唯一

首先检查修改输入值是否会改变我们的状态。

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 函数调用。它是前面提到过的 ShallowWrapper 的功能。我们用它来模拟事件。第一个参数是事件的类型(由于在输入中使用了 onChange,因此在这里应该用 change),第二个参数是模拟事件对象。

为了更进一步,让我们测试一下用户单击按钮后是否从的组件发送了实际的请求。

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();
    });
  });
});

测试通过了!

现在事情会变得有些棘手。我们将要测试状态是否能够随着的新任务而更新。有趣的是请求是异步的。

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 是能够确保 Jest 等待其解决的一种方法。


总结

在本文中,我们介绍了模拟模块,并将其用于伪造 API 调用。由于没有发出实际的请求要求,我们的测试可以更可靠、更快。除此之外,我们还在整个 React 组件中模拟了事件,并检查了它是否产生了预期的结果,例如组件的请求或状态变化,并且了解了监视的概念。

原文:https://wanago.io/2018/09/17/javascript-testing-tutorial-part-four-mocking-api-calls-and-simulation-react-components-interactions/


链接: https://fly63.com/article/detial/6499

测试工具比较:选Jest,不选Mocha

Jest的未来看起来非常令人激动!看到Jest推陈出新如此快速,我感觉它将很快成为整个React生态系统中大部分项目的首选工具。我建议,应该把测试迁移到Jest上去。

你需要了解的前端测试“金字塔”

如果您正在测试前端应用程序,则应该了解前端测试金字塔。在本文中,我们将看到前端测试金字塔是什么,以及如何使用它来创建全面的测试套件。

web网页性能测试工具都有哪些

作为前端开发,我们不仅需要满足产品需求功能的实现,同时也需要对自己做的网站进行安全、易用性、性能等方面的考虑。随着目前技术不断进步,web页面的性能测试工具也在不断完善,通过这些工具,我们可以客观的评价web网站的质量水平。

js单元测试工具-jest自动化测试

jest 是 facebook 开源的,用来进行单元测试的框架,可以测试 javascipt 和 react。jest 提供了非常方便的 API,可以对下面的场景方便的测试:一般函数、异步函数、测试的生命周期、react 测试

web测试要点、方法_web端测试大全总结

web测试大全,测试web网站有哪些点呢?主要包括:功能测试、兼容性测试、安全测试、输入框测试、用户权限测试等

前端性能测试工具整理简介_性能测试工具都有哪些?

前端性能测试工具都有哪些:Favicon、Open Graph、图片优化-压缩图像、CSS 优化-Autoprefixer、Purifycss、minify CSS、减少载入时间、GZIP、CDN、优化平台-Sentry、Google Tag Manager

不用写代码,也能做好接口测试

本文你将了解到:1、接口测试基本概念,包含什么是接口,什么是接口测试,为什么要做接口测试;2、接口测试用例设计,3、怎样不用写代码,也能快速的根据开发的API文档完成接口自动化测试脚本

Selenium打开浏览器加载慢的原因

在自动化元素定位操作中经常使用智能等待来加强定位的强壮性,主要就是因为WebDriver没有提供页面加载场景的方法;在使用JavaScript知识的突然心生灵感,可以使用JavaScript来配合验证页面加载,结果发现我真是井底之蛙。

power assert_更智能、优雅的全方位 assert 断言库

在写测试代码时,以往我们需要翻阅文档,学习各种 API 才能明白如何操作断言。而现在我们可以透过 power-assert 的 assert 方法来减轻调试压力。不仅如此,它还提供更加直观,具体的运行效果,帮助 DEBUG。写测试代码,其实可以很容易。

常用的web网站负载/压力/性能测试工具

在网站上线发布之前,我们除了必要的安全、功能测试外,往往还需要进行压力测试。通过模拟实际应用的软硬件环境及用户使用过程的系统负荷,长时间或超大负荷地运行测试软件。包括:Apache JMeter 、LoadRunner、NeoLoad等

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!