zoukankan      html  css  js  c++  java
  • 模拟 API 调用和模拟 React 组件交互

    模拟

    对于我们的程序来说,从 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();
        });
      });
    });

    资源搜索网站大全 https://www.renrenfan.com.cn 广州VI设计公司https://www.houdianzi.com

    模拟 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 等待其解决的一种方法。

  • 相关阅读:
    windows下安装rocketmq采坑全记录
    测试日常使用---网络协议与抓包
    python重写及重写后调用父类方法
    python继承和多态
    python私有成员都以双下划线“__”开头,仅类内部可访问
    http中的post请求发生了两次(多了一次options请求)的原因
    测试日常使用---linux命令:
    数据库性能优化
    pytest常用配置文件之pytest.ini
    pytest.main()的使用
  • 原文地址:https://www.cnblogs.com/xiaonian8/p/14124429.html
Copyright © 2011-2022 走看看