溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

JavaScript中如何使用Mock模擬模塊并處理組件交互

發(fā)布時間:2021-09-30 10:30:23 來源:億速云 閱讀:178 作者:柒染 欄目:大數(shù)據

本篇文章為大家展示了JavaScript中如何使用Mock模擬模塊并處理組件交互,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。

我們將學習如何測試更復雜的組件,包括用 Mock 去編寫涉及外部 API 的測試,以及通過 Enzyme 來輕松模擬組件交互

 

初次嘗試 Jest Mock

我們的應用程序通常需要從外部的 API 獲取數(shù)據。在編寫測試時,外部 API 可能由于各種原因而失敗。我們希望我們的測試是可靠和獨立的,而最常見的解決方案就是 Mock。

 

改寫 TodoList 組件

首先讓我們改造組件,使其能夠通過 API 獲取數(shù)據。安裝 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 生命周期函數(shù)中通過 axios 模塊異步獲取數(shù)據。

 

編寫 axios 模塊的 mock 文件

Jest 支持對整個模塊進行 Mock,使得組件不會調用原始的模塊,而是調用我們預設的 Mock 模塊。按照官方推薦,我們創(chuàng)建 mocks 目錄并把 mock 文件放到其中。創(chuàng)建 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 函數(shù),并且會返回一個 Promise,包含預先設定的假數(shù)據。

 

通過 spyOn 函數(shù)檢查 Mock 模塊調用情況

讓我們開始 Mock 起來!打開 TodoList 的測試文件,首先在最前面通過 jest.mock 配置 axios 模塊的 Mock(確保要在 import TodoList 之前),在 Mock 之后,無論在測試還是組件中使用的都將是 Mock 版本的 axios。然后創(chuàng)建一個測試用例,檢查 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();
    });
  });
});
 

測試模塊中一個函數(shù)是否被調用實際上是比較困難的,但是所幸 Jest 為我們提供了完整的支持。首先通過 jest.spyOn,我們便可以監(jiān)聽一個函數(shù)的使用情況,然后使用配套的 toBeCalled Matcher 來判斷該函數(shù)是否被調用。整體代碼十分簡潔,同時也保持了很好的可讀性。

如果你忘記了 Jest Matcher 的含義,推薦閱讀本系列的第一篇教程。

 

迭代 TodoList 組件

一個實際的項目總會不斷迭代,當然也包括我們的 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>
`;
   

在測試中模擬 React 組件的交互

在上面迭代的 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 的唯一性。

讓我們開始測試吧!我們測試的第一件事是檢查修改輸入值是否更改了我們的狀態(tài):

我們修改 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] 函數(shù)的調用。這是我們幾次提到的ShallowWrapper的功能。我們用它來模擬事件。它第一個參數(shù)是事件的類型(由于我們在輸入中使用onChange,因此我們應該在此處使用change),第二個參數(shù)是模擬事件對象(event)。

為了進一步說明問題,讓我們測試一下用戶單擊按鈕后是否從我們的組件發(fā)送了實際的 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 事件,測試通過了!現(xiàn)在事情會變得有些棘手。我們將測試狀態(tài)是否隨著我們的新任務而更新,其中比較有趣的是請求是異步的,我們繼續(xù)修改代碼如下:

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 函數(shù)發(fā)送結果的數(shù)組,通過使用它,我們可以得到返回的 promise,我們可以從 value 屬性中取到這個 promise。從測試返回 promise 是確保 Jest 等待其異步方法執(zhí)行結束的一種方法。

 

小結

在本文中,我們介紹了 mock 模塊,并將其用于偽造API調用。由于沒有發(fā)起實際的 post 請求,我們的測試可以更可靠,更快。除此之外,我們還在整個 React 組件中模擬了事件。我們檢查了它是否產生了預期的結果,例如組件的請求或狀態(tài)變化。為此,我們了解了 spy 的概念。

 

嘗試測試 React Hooks

Hooks 是 React 的一個令人興奮的補充,毫無疑問,它可以幫助我們將邏輯與模板分離。這樣做使上述邏輯更具可測試性。不幸的是,測試鉤子并沒有那么簡單。在本文中,我們研究了如何使用 react-hooks-testing-library[2] 處理它。

我們創(chuàng)建 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 可以輕松地管理模式狀態(tài)。讓我們開始測試它是否不會引發(fā)任何錯誤,我們創(chuàng)建 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 只能在函數(shù)式組件的函數(shù)體內部調用。

 

讓測試通過

React文檔[3] 里面提到:我們只能從函數(shù)式組件或其他 Hooks 中調用 Hooks。我們可以使用本系列前面部分介紹的 enzyme 庫來解決此問題,而且使了一點小聰明,我們創(chuàng)建 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;
 

我們繼續(xù)迭代 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模擬模塊并處理組件交互,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI