有时候我们需要不发送请求就能完成前端的业务逻辑测试,而许多的业务逻辑都会需要调用到后端的API接口。那如何能mock我们所需要的data就是一个问题。当我们能有一个良好的测试环境之后,只要保证后端的接口没有问题,那我们就可以保证业务逻辑也没有问题。
所以我们对API的集成测试有以下几个要求
1.不发送请求,返回本地假数据
2.发布前通过CI跑unit test,通过则发布上线
如何实现?
首先一般我们在network部分都会进行封装,假设在project中封装了如下的请求工具
// http tool
export default function http() {
// some implement
}
复制代码
既然我们不能发送真实请求,那我们就需要类似能拦截的东西,拦截也可以通过mock代替。于是我们可以通过jest.mock
方法来做。
jest.mock
// api/http.js
// real fn
export default function http() {
console.log('real');
//...
}
复制代码
// api/__mocks__/http.js
// fake fn
export default function http() {
console.log('fake');
}
复制代码
// some.test.js
jest.mock('../http')
import http from '../http'
http() // 这里log的是fake,而不是real
复制代码
这个就是jest.mock
的作用。
正事
明白了这个后就好办了。项目目录如下:
-- api
|-- __mockData__
|-- user.data.js
|-- __mocks__
|-- http.js
|-- __tests__
|-- user.test.js
|-- http.js
|-- profile // profile 业务模块
|-- user.js // 获取用户信息
复制代码
而我们的fake文件其实主要做的事情就是根据请求url,method,status等,去读取对应的本地假数据。大致如下。
// ./api/__mocks__/http.js
// 直接读取本地假数据
let statusCode;
export function setStatus(code) {
statusCode = code;
}
export default function http({ url = "", data = {}, method = "get" }) {
return new Promise((resolve, reject) => {
const lastSlash = url.lastIndexOf("/");
const module = url.substring(lastSlash + 1);
const mockData = require(`../__mockData__/${module}.data`).default;
const result = mockData[`${method.toUpperCase()} ${statusCode}`];
process.nextTick(
() => (statusCode === 200 ? resolve(result) : reject(result))
);
});
}
复制代码
mockData文件夹则就是放我们的假数据,在这我们可以假设定义如下数据结构,来模拟我们的response
// ./api/__mockData__/user.js
export default {
'GET 200': {
code: 0,
msg: 'ok',
data: {
username: '二哲',
age: 18,
},
},
'POST 200': {
code: 0,
msg: 'xxx',
},
'GET 400': {
msg: 'invald params',
code: -1,
},
'GET 401': {},
};
复制代码
最后看下我们的 unit test 如何写
// ./api/__test__/user.test.js
jest.mock('../http') // jest 会自动搜索目录下的 __mocks__里的文件
import http from '../http';
describe('user api test', () => {
it("user GET should be 200", async () => {
setStatus(200);
const result = await http({
url,
method: "get"
});
expect(result.data.username).toBe("Kodo");
});
})
复制代码
实现了这个有什么用?
假设/user
接口返回得数据可能是这样
{
"username": "二哲",
"age": 18,
}
复制代码
而我们前端service层为UI层提供了一个initUserData的方法,initUserData方法里的操作是当age
为18,那就要返回19。
所以我们在Jest则可以直接这样测试
// ./api/__test__/user.test.js
jest.mock('../http') // jest 会自动搜索目录下的 __mocks__里的文件
import { setStatus } from './http';
import { initUserData } from '../user'
describe('user api test', () => {
it("if user age is 18, age should be 19", async () => {
expect.assertions(1);
setStatus(200);
const result = await initUserData();
// console.log(result);
expect(result.data.age).toBe(19);
});
// test catch
it("initUserData 400", async () => {
expect.assertions(1);
setStatus(400);
const result = await initUserData();
expect(result.msg).toBe("invald params");
});
})
复制代码
这样我们使用Jest就可以完成对业务逻辑的测试,Unit test在大型项目中非常需要,每当提交一个feature时,可以跑完所有测试,会让你非常有安全感,极大提升了项目的稳定性。
TIP
真正的方法(http),与mock的方法http,文件必须同名,然后放在mocks文件夹下即可。如果不同名使用jest.mock()则会失败。
以上例子都在这 jest-api-test