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

創(chuàng)新互聯(lián)是一家專注于成都網(wǎng)站設(shè)計(jì)、網(wǎng)站制作與策劃設(shè)計(jì),徐聞網(wǎng)站建設(shè)哪家好?創(chuàng)新互聯(lián)做網(wǎng)站,專注于網(wǎng)站建設(shè)10余年,網(wǎng)設(shè)計(jì)領(lǐng)域的專業(yè)建站公司;建站業(yè)務(wù)涵蓋:徐聞等地區(qū)。徐聞做網(wǎng)站價(jià)格咨詢:028-86922220
我們將學(xué)習(xí)如何測(cè)試更復(fù)雜的組件,包括用 Mock 去編寫涉及外部 API 的測(cè)試,以及通過(guò) Enzyme 來(lái)輕松模擬組件交互
我們的應(yīng)用程序通常需要從外部的 API 獲取數(shù)據(jù)。在編寫測(cè)試時(shí),外部 API 可能由于各種原因而失敗。我們希望我們的測(cè)試是可靠和獨(dú)立的,而最常見(jiàn)的解決方案就是 Mock。
首先讓我們改造組件,使其能夠通過(guò) API 獲取數(shù)據(jù)。安裝 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 被改造成了一個(gè)“聰明組件”,在 componentDidMount 生命周期函數(shù)中通過(guò) axios 模塊異步獲取數(shù)據(jù)。
Jest 支持對(duì)整個(gè)模塊進(jìn)行 Mock,使得組件不會(huì)調(diào)用原始的模塊,而是調(diào)用我們預(yù)設(shè)的 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 模塊提供了一個(gè) get 函數(shù),并且會(huì)返回一個(gè) Promise,包含預(yù)先設(shè)定的假數(shù)據(jù)。
讓我們開(kāi)始 Mock 起來(lái)!打開(kāi) TodoList 的測(cè)試文件,首先在最前面通過(guò) jest.mock 配置 axios 模塊的 Mock(確保要在 import TodoList 之前),在 Mock 之后,無(wú)論在測(cè)試還是組件中使用的都將是 Mock 版本的 axios。然后創(chuàng)建一個(gè)測(cè)試用例,檢查 Mock 模塊是否被正確調(diào)用。代碼如下:
// 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();
});
});
});
測(cè)試模塊中一個(gè)函數(shù)是否被調(diào)用實(shí)際上是比較困難的,但是所幸 Jest 為我們提供了完整的支持。首先通過(guò) jest.spyOn,我們便可以監(jiān)聽(tīng)一個(gè)函數(shù)的使用情況,然后使用配套的 toBeCalled Matcher 來(lái)判斷該函數(shù)是否被調(diào)用。整體代碼十分簡(jiǎn)潔,同時(shí)也保持了很好的可讀性。
如果你忘記了 Jest Matcher 的含義,推薦閱讀本系列的第一篇教程。
一個(gè)實(shí)際的項(xiàng)目總會(huì)不斷迭代,當(dāng)然也包括我們的 TodoList 組件。對(duì)于一個(gè)待辦事項(xiàng)應(yīng)用來(lái)說(shuō),最重要的當(dāng)然便是添加新的待辦事項(xiàng)。
修改 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;
由于我們大幅改動(dòng)了 TodoList 組件,我們需要更新快照:
npm test -- -u
如果你不熟悉 Jest 快照測(cè)試,請(qǐng)回看本系列第二篇教程。
更新后的快照文件反映了我們剛剛做的變化:
// 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。這意味著我們需要擴(kuò)展 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++,
},
},
});
},
};
可以看到上面,我們添加了一個(gè)
currentId變量,因?yàn)槲覀冃枰3置總€(gè) task 的唯一性。
讓我們開(kāi)始測(cè)試吧!我們測(cè)試的第一件事是檢查修改輸入值是否更改了我們的狀態(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);
});
});
});
這里要重點(diǎn)指出的就是 simulate[1] 函數(shù)的調(diào)用。這是我們幾次提到的ShallowWrapper的功能。我們用它來(lái)模擬事件。它第一個(gè)參數(shù)是事件的類型(由于我們?cè)谳斎胫惺褂胦nChange,因此我們應(yīng)該在此處使用change),第二個(gè)參數(shù)是模擬事件對(duì)象(event)。
為了進(jìn)一步說(shuō)明問(wèn)題,讓我們測(cè)試一下用戶單擊按鈕后是否從我們的組件發(fā)送了實(shí)際的 post 請(qǐng)求。我們修改測(cè)試代碼如下:
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 事件,測(cè)試通過(guò)了!現(xiàn)在事情會(huì)變得有些棘手。我們將測(cè)試狀態(tài)是否隨著我們的新任務(wù)而更新,其中比較有趣的是請(qǐng)求是異步的,我們繼續(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ā)送結(jié)果的數(shù)組,通過(guò)使用它,我們可以得到返回的 promise,我們可以從 value 屬性中取到這個(gè) promise。從測(cè)試返回 promise 是確保 Jest 等待其異步方法執(zhí)行結(jié)束的一種方法。
在本文中,我們介紹了 mock 模塊,并將其用于偽造API調(diào)用。由于沒(méi)有發(fā)起實(shí)際的 post 請(qǐng)求,我們的測(cè)試可以更可靠,更快。除此之外,我們還在整個(gè) React 組件中模擬了事件。我們檢查了它是否產(chǎn)生了預(yù)期的結(jié)果,例如組件的請(qǐng)求或狀態(tài)變化。為此,我們了解了 spy 的概念。
Hooks 是 React 的一個(gè)令人興奮的補(bǔ)充,毫無(wú)疑問(wèn),它可以幫助我們將邏輯與模板分離。這樣做使上述邏輯更具可測(cè)試性。不幸的是,測(cè)試鉤子并沒(méi)有那么簡(jiǎn)單。在本文中,我們研究了如何使用 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)。讓我們開(kāi)始測(cè)試它是否不會(huì)引發(fā)任何錯(cuò)誤,我們創(chuàng)建 useModalManagement.test.js
// src/useModalManagement.test.js
import useModalManagement from './useModalManagement';
describe('The useModalManagement hook', () => {
it('should not throw an error', () => {
useModalManagement();
});
});
我們運(yùn)行測(cè)試,得到如下的結(jié)果:
FAIL useModalManagement.test.js
The useModalManagement hook
? should not throw an error按 ?+? 退出
不幸的是,上述測(cè)試無(wú)法正常進(jìn)行。我們可以通過(guò)閱讀錯(cuò)誤消息找出原因:
無(wú)效的 Hooks 調(diào)用, Hooks 只能在函數(shù)式組件的函數(shù)體內(nèi)部調(diào)用。
React文檔[3] 里面提到:我們只能從函數(shù)式組件或其他 Hooks 中調(diào)用 Hooks。我們可以使用本系列前面部分介紹的 enzyme 庫(kù)來(lái)解決此問(wèn)題,而且使了一點(diǎn)小聰明,我們創(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,修改內(nèi)容如下:
// src/useModalManagement.test.js
import useModalManagement from './useModalManagement';
import testHook from './testHook';
describe('The useModalManagement hook', () => {
it('should not throw an error', () => {
testHook(useModalManagement);
});
});
我們?cè)试S測(cè)試,得到如下結(jié)果:
PASS useModalManagement.test.js
The useModalManagement hook
? should not throw an error
好多了!但是,上述解決方案不是很好,并且不能為我們提供進(jìn)一步測(cè)試 Hooks 的舒適方法。這就是我們使用 react-hooks-testing-library[4] 的原因。
上述內(nèi)容就是JavaScript中如何使用Mock模擬模塊并處理組件交互,你們學(xué)到知識(shí)或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識(shí)儲(chǔ)備,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。
分享標(biāo)題:JavaScript中如何使用Mock模擬模塊并處理組件交互
標(biāo)題URL:http://www.chinadenli.net/article24/iiicje.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供App開(kāi)發(fā)、網(wǎng)站內(nèi)鏈、網(wǎng)站設(shè)計(jì)公司、品牌網(wǎng)站制作、微信小程序、響應(yīng)式網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)