做者:Marcin Wanagojavascript
翻譯:瘋狂的技術宅html
未經容許嚴禁轉載java
今天,咱們進一步測試 React 組件。它涉及模擬組件交互和模擬 API 調用。你將學到兩種方法,開始吧!react
對於咱們的程序來講,從 API 獲取一些數據是很常見的。可是它可能因爲各類緣由而失敗,例如 API 被關閉。咱們但願測試可靠且獨立,並確保能夠模擬某些模塊。咱們把 ToDoList 組件修改成智能組件。webpack
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 __ 目錄中定義,在該目錄中,文件名被視爲模擬模塊的名稱。ios
'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')web
Jest 容許咱們對函數進行監視:接下來測試是否調用了咱們所建立的 get 函數。json
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。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
}
複製代碼
另外一個常見狀況是使用 Fetch API。一個竅門是它是附加到 window 對象的全局函數並對其進行模擬,能夠將其附加到 global 對象。首先,讓咱們建立模擬的 fetch 函數。
export default function() {
return Promise.resolve({
json: () =>
Promise.resolve([
{
id: 0,
name: 'Wash the dishes'
},
{
id: 1,
name: 'Make the bed'
}
])
})
}
複製代碼
而後,將其導入 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
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();
});
});
});
複製代碼
在以前的文章中,咱們提到了閱讀組件的狀態或屬性,但這是在實際與之交互時。爲了說明這一點,咱們將增長一個把任務添加到 ToDoList 的功能。
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 模擬。
'use strict';
let currentId = 2;
module.exports = {
get: (url) => {
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惟一
首先檢查修改輸入值是否會改變咱們的狀態。
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 組件中模擬了事件,並檢查了它是否產生了預期的結果,例如組件的請求或狀態變化,而且瞭解了監視的概念。