您好,登錄后才能下訂單哦!
本篇文章為大家展示了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進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。