跳转到内容

使用 Pollyjs 进行 HTTP 请求测试

前端开发者独立于后端进行开发,往往需要对请求数据进行 mock。但如果在业务代码中进行修改,后期再进行修改不免要小心翼翼。 所以现有工具都会帮我们拦截请求,但如果针对初创项目来说,修改返回数据是不可避免的(graphql 把大部分数据修改置于前端),维护 mock 数据和线上数据保持一致是相对困难的。

这时候我们可以尝试使用 Pollyjs

Polly.JS 是一个独立的、与框架无关的 JavaScript 库,支持 HTTP 交互的记录、重放和存储。可以在 node 和浏览器中使用。Polly.JS 可以控制每个请求,并对不通请求模拟不同的响应。

当然 Polly.JS 最大的作用是做测试,不过该库也可以用于数据 mock。

Pollyjs 没有采用编写数据的方案,而是把线上接口返回的数据持久化,以便开发者使用以及测试。

import { Polly } from '@pollyjs/core'
import FetchAdapter from '@pollyjs/adapter-fetch'
import XHRAdapter from '@pollyjs/adapter-xhr'
import LocalStoragePersister from '@pollyjs/persister-local-storage'
// 注册 fetch 和 xhr 请求 劫持
Polly.register(FetchAdapter)
Polly.register(XHRAdapter)
// 注册持久化方法,使用 localStorage
Polly.register(LocalStoragePersister)
new Polly('<Recording Name>', {
// 添加各种不同的适配器
adapters: ['fetch', 'xhr'],
// 当前持久化存储
persister: 'local-storage',
// 控制台打印
logging: true,
// 不同的模式
// record 强制记录服务器返回的数据,这将覆盖已有的持久化数据
// replay 获取记录中的数据并响应
// passthrough 不采用记录以及重放,直接使用服务端数据
mode: 'record',
// 如果当前记录丢失,就去服务端获取
recordIfMissing: true
})
new Polly('<Recording Name>2')
// 也可以使用 configure 进行配置
polly.configure({
recordIfMissing: false
});
// 获取数据,当前请求将会被 Polly 劫持
const response = await fetch(
'https://jsonplaceholder.typicode.com/posts/1'
);
const post = await response.json()
// 断开连接
await polly.stop()
return post

一旦调用了 stop 方法,持久器将生成以下 HAR 文件,该文件将用于在重新运行时直接返回响应,不会向服务端请求。

{
"Simple-Example_823972681": {
"log": {
"_recordingName": "Simple Example",
"browser": {
"name": "Chrome",
"version": "70.0"
},
"creator": {
"comment": "persister:local-storage",
"name": "Polly.JS",
"version": "1.2.0"
},
"entries": [
{
"_id": "ffbc4836d419fc265c3b85cbe1b7f22e",
"_order": 0,
"cache": {},
"request": {
"bodySize": 0,
"cookies": [],
"headers": [],
"headersSize": 63,
"httpVersion": "HTTP/1.1",
"method": "GET",
"queryString": [],
"url": "https://jsonplaceholder.typicode.com/posts/1"
},
"response": {
"bodySize": 292,
"content": {
"mimeType": "application/json; charset=utf-8",
"size": 292,
"text": "{\n \"userId\": 1,\n \"id\": 1,\n \"title\": \"sunt aut facere repellat provident occaecati excepturi optio reprehenderit\",\n \"body\": \"quia et suscipit\\nsuscipit recusandae consequuntur expedita et cum\\nreprehenderit molestiae ut ut quas totam\\nnostrum rerum est autem sunt rem eveniet architecto\"\n}"
},
"cookies": [],
"headers": [
{
"name": "cache-control",
"value": "public, max-age=14400"
},
{
"name": "content-type",
"value": "application/json; charset=utf-8"
},
{
"name": "expires",
"value": "Tue, 30 Oct 2018 22:52:42 GMT"
},
{
"name": "pragma",
"value": "no-cache"
}
],
"headersSize": 145,
"httpVersion": "HTTP/1.1",
"redirectURL": "",
"status": 200,
"statusText": "OK"
},
"startedDateTime": "2018-10-30T18:52:42.566Z",
"time": 18,
"timings": {
"blocked": -1,
"connect": -1,
"dns": -1,
"receive": 0,
"send": 0,
"ssl": -1,
"wait": 18
}
}
],
"pages": [],
"version": "1.2"
}
}
}

如此,我们可以做持久化数据来进行各种测试。

开发者也可以使用框架来修改响应数据做测试。

describe('Intercept', function() {
setupPolly({
adapters: ['fetch'],
persister: 'local-storage'
});
it('can mock valid responses', async function() {
const { server } = this.polly;
// 此处劫持用户请求,响应 mock 数据
server
.get('https://jsonplaceholder.typicode.com/posts/:id')
.intercept((req, res) => {
res.status(200).json({
id: Number(req.params.id),
title: `Post ${req.params.id}`
});
});
const res = await fetch('https://jsonplaceholder.typicode.com/posts/42');
const post = await res.json();
expect(res.status).to.equal(200);
expect(post.id).to.equal(42);
expect(post.title).to.equal('Post 42');
});
it('can mock invalid responses', async function() {
const { server } = this.polly;
server
.get('https://jsonplaceholder.typicode.com/posts/404')
.intercept((_, res) => {
res.status(404).send('Post not found.');
});
const res = await fetch('https://jsonplaceholder.typicode.com/posts/404');
const text = await res.text();
expect(res.status).to.equal(404);
expect(text).to.equal('Post not found.');
});
it('can conditionally intercept requests', async function() {
const { server } = this.polly;
server
.get('https://jsonplaceholder.typicode.com/posts/:id')
.intercept((req, res, interceptor) => {
if (req.params.id === '42') {
res.status(200).send('Life');
} else {
// 中断当前拦截,向服务端端进行请求
interceptor.abort();
}
});
let res = await fetch('https://jsonplaceholder.typicode.com/posts/42');
expect(res.status).to.equal(200);
expect(await res.text()).to.equal('Life');
res = await fetch('https://jsonplaceholder.typicode.com/posts/1');
expect(res.status).to.equal(200);
expect((await res.json()).id).to.equal(1);
});
});

我们可以结合测试工具对项目添加请求测试,以便代码修改。

在进行项目开发的过程中,即使是有经验的开发者,也很有可能忽略网络问题带来的错误。

开发者往往拥有极快的网速。但是很多情况下,用户未必向我们所想的那样,他们对于网速的需求可能没有那么大,也不愿意为网络支付过高的费用。

比如在移动端的列表页面,用户会通过下拉界面来获取新的数据。但是如果开发者忽略网页重复数据的可能。 这时候,我们可能就需要添加底部加载图标和 loading 状态,在此状态下不进行请求,只有完结后再次进行请求。

当然,如果仅仅只是网络请求的快慢,我们只需要在浏览器上修改网络获取速度即可。但当前修改是针对所有的网络请求,如果仅仅只考虑单一请求带来的影响,浏览器就没有办法了,我们需要借助 Polly.js 。

import { Polly } from '@pollyjs/core'
const polly = new Polly('<Recording Name>2', {
// 添加适配器
})
const { server } = polly
// 在获取数据时拦截请求并且设置 500ms 延迟
server.get('/ping').intercept(async (req, res) => {
await server.timeout(500);
res.sendStatus(200);
})

当然. Pollyjs 也有更多的使用方法,我也会进一步学习以及分享该库。