API优先设计:构建可扩展且灵活的软件的现代方法
如何在测试环境中模拟 API 调用
自动化 API 测试是确保软件质量的好方法。它可以帮助您确定期望看到的行为,并在遇到错误时为您提供有关问题所在的详细信息。如今,API 无处不在,但将 API 集成到 API 单元测试中并找到优秀的 API 测试人员可能有点棘手。
但是,无论您的 API 仍在开发中,还是您正在开发新功能,系统地测试预期行为都可以节省大量时间并更轻松地识别问题。开发模拟 API 调用可以帮助您使用有价值的单元测试,而不会出现与调用实时 API 相关的问题。
Node.js 为行为驱动的 JavaScript 提供了几种不同的自动化测试环境,包括两个流行的框架 Jest 和 Jasmine,我们将在本文中对它们进行比较。首先,我们将讨论使用 API 进行行为驱动开发的最佳实践和技巧。然后,我们将介绍如何在 Jasmine 或 Jest 中为您的 API 设置和运行测试。最后,我们将比较这两个框架,以帮助您确定哪个最适合您的需求。
为什么要模拟 API 调用?
也许您在读这篇文章时会想:测试很棒,但为什么不测试实际的 API 呢?您可以使用 Axios 之类的库从网络发出 HTTP 请求。如果您正在开发 API,它可以帮助您在部署之前在本地进行规划。如果您的 API 已经部署,但您正在添加新功能,那么您肯定不想将未经测试的代码推送到实时版本。此外,您可能想要测试一个因过度使用而收费或具有不确定结果(例如来自数据库的动态数据)的 API。模拟 API 调用可让您在这些情况下进行控制,并加快后续开发速度。行为驱动开发的第一步是列出您的 API 在正常运行时应该做的所有事情。每一项都应该具体、可衡量且确定。如果您有写得很好的文档,这可能是您的起点。如果您刚刚开始,那么这个初步计划可能会变成您的文档。
对于每个步骤,列出一个示例,说明此请求的原始数据是什么样子以及响应应该是什么样子。例如:
- 请求时返回带有用户帖子的 200 状态
- 请求:{method:’get’,body:{user:“Jack”}}
- 回复:{status:200,posts:posts:[“我刚买了一些魔豆!”]}
为了在不连接网络的情况下测试这些行为,您应该将此逻辑与主要路由分开。这样,您就可以在测试中轻松导入 API 所依赖的相同函数。您可以创建一个函数来处理请求对象并返回一个承诺来模拟异步模拟 API 调用。它可能看起来像这样:
function simulateAsyncCall(request) {
return new Promise((resolve, reject) => {
setTimeout(() => {
switch (request.method) {
case 'get':
const user = getUser(request);
if (user) {
resolve({ status: 200, posts: user.posts });
} else {
resolve({ status: 404, message: 'Not Found' });
}
break;
case 'post':
if (passwordIsValid(request)) {
addToPosts(request);
resolve({ status: 200, message: 'Added Post' });
} else {
resolve({ status: 401, message: 'Unauthorized' });
}
break;
default:
resolve({ status: 400, message: 'Bad Request' });
}
}, 300);
});
}
如何使用 Jasmine 创建 Mock API?
在安装任何 Node 模块之前,你应该确保你的根目录中有一个 package.json 文件。如果没有,你可以使用以下命令进行设置:npm init -y
。
让我们首先在你的项目目录中安装 Jasmine:npm install jasmine –save-dev
完成后,您可以使用以下命令配置 Jasmine:node node_modules/jasmine/bin/jasmine.js init
这将创建一个 spec 文件夹,其中包含一些具有默认设置的配置文件。请务必记住,我们编写的测试文件应以“spec.js”结尾
最后,我们需要在 package.json 文件中设置测试命令。打开此文件并向对象添加一个包含 Jasmine 模块路径的test
键。它应该如下所示:scripts
"scripts":{
"test":"jasmine"
}
让我们确保所有设置都正确。在spec
名为my-first-spec.js
paste this 的文件夹中创建一个名为的文件:
describe('My Jasmine Setup', function () {
var a = true;
it('tests if the value of a is true', function () {
expect(a).toBe(true);
});
});
您可以通过运行我们在 package.json 中放入的测试脚本在终端中运行测试:npm run test
您的测试应该会通过!尝试将值更改a
为 false,然后再次运行它,看看测试失败时会是什么样子。请注意,当出现问题时,它会向您提供描述性消息。
Jasmine 测试套件的另一个很酷的功能是“beforeEach”和“afterEach”函数。这允许您在每个“it”块之前或之后执行某些操作。让我们在每次测试之前创建一个新的 MockAPI 类实例。
以下脚本可以在 Jasmine 或 Jest 中运行:
const MockAPI= require('../MockAPI.js');
describe("Mock API",()=>{
let mockAPI;
let mockDatabase=
{
users:[
{
name:"Jack",
passwordHash:"dasdKDKDJSLASDLASDJSAasdsdc123",
posts:["I just bought some magic beans!"]
},
{
name:"Jill",
passwordHash:"dasdKDKDJSLASDLASDJSAasdsdc123"
posts:["Jack fell down!"]
},
]
};
beforeEach(()=>{
mockAPI= new MockAPI(mockDatabase)
})
it("returns a 400 bad request status if the request is invalid",()=>{
const mockApiCall=mockAPI.simulateAsyncCall({})
return mockApiCall.then(response=>{
expect(response.status).toBe(400)
})
})
describe("get requests",()=>{
const validRequest={method:'get',body:{user:"Jack"}};
const invalidRequest={method:'get',body:{user:"Tod"}};
it("returns a 404 status if a user is not found",()=>{
const mockApiCall=mockAPI.simulateAsyncCall(invalidRequest)
return mockApiCall.then(response=>{
expect(response.status).toBe(404)
})
});
it("returns a 200 status with a user's posts",()=>{
const mockApiCall=mockAPI.simulateAsyncCall(validRequest)
return mockApiCall.then(response=>{
expect(response.status).toBe(200)
expect(response.posts).toEqual(["I just bought some magic beans!"])
})
});
})
describe("post requests",()=>{
const validRequest={method:'post',body:{user:"Jill",password:'hill',post:"He broke his crown!"}}
const invalidRequest={method:'post',body:{user:"Jill",password:'beanstock',post:"Jack is cool..."}}
it("returns a 401 unauthorized status if the wrong credentials are sent",()=>{
const mockApiCall=mockAPI.simulateAsyncCall(invalidRequest)
return mockApiCall.then(response=>{
expect(response.status).toBe(401)
expect(mockAPI.db).toEqual(mockDatabase)
})
})
it("returns a 200 status and adds the post to the database",()=>{
const newDatabase={
users:[
{
name:"Jack",
passwordHash:"dasdKDKDJSLASDLASDJSAasdsdc123"
posts:["I just bought some magic beans!"]
},
{
name:"Jill",
passwordHash:"dasdKDKDJSLASDLASDJSAasdsdc123"
posts:["Jack fell down!","He broke his crown!"]
},
]
}
const mockApiCall=mockAPI.simulateAsyncCall(validRequest)
return mockApiCall.then(response=>{
expect(response.status).toBe(200)
expect(mockAPI.db).toEqual(newDatabase)
})
})
})
})
如何使用 Jest 模拟 API 调用?
要开始使用 Jest,你只需要安装它:npm install jest –save-dev
并在你的 package.json 文件中包含一个测试命令,如下所示:
"scripts":{
"test":" jest"
}
Jest 最初是 Jasmine 的一个分支,因此您可以执行我们上面描述的所有操作,甚至更多。“describe”块的基本模式和包含一个或多个“expect”方法的“it”块在 Jest 中的工作原理相同。
到目前为止,我们一直在测试确定性函数(对于给定的输入,它们始终具有相同的输出)。但如果我们的 API 依赖于我们无法控制的东西,该怎么办?例如,如果我们的 API 使用第三方登录进行身份验证,该怎么办?
Jest 允许您创建模拟函数,该函数返回可预测的结果,并包含额外的方法来跟踪函数如何与 API 集成。使用 jest.fn 方法,我们可以做出如下断言:
describe('AJAX functions with Jest', () => {
const mockUrl = '/api/users';
const mockUsers = [{ name: 'jack', name: 'jill' }];
const getUsers = jest.fn(url => mockUsers);
it('returns returns users from an api call', () => {
expect(getUsers(mockUrl)).toBe(mockUsers);
console.log(getUsers);
});
it('called getUser with a mockUrl', () => {
expect(getUsers).toHaveBeenCalledWith(mockUrl);
});
});
如果你运行这个测试并查看 console.log,你会注意到有很多方法与这个模拟函数相关联。这些方法允许你具体定义函数的调用方式、函数应返回的内容等等。
您还可以使用 模拟整个模块(用 jest mock 函数替换它们的方法) 。例如,您可以导入 HTTP 库(例如 Axios)并像这样jest.mock()
设置其方法的返回值:.get()
const axios = require('axios');
jest.mock('axios');
class Users {
static all() {
return axios.get('/users.json').then(resp => resp.data);
}
}
const mockUsers = [{ name: 'Jack' }];
const mockResponse = { data: mockUsers };
axios.get.mockResolvedValue(mockResponse);
return Users.all().then(data => expect(data).toEqual(users));
Jasmine 还有一个用于模拟 AJAX 调用的插件 ( jasmine-ajax ),但它不像 Jest 那样灵活。它用自定义响应替换浏览器中的 XMLHttpRequest 对象。由于 XMLHttpRequest 存在于 DOM 中,因此您需要创建一个假 DOM(使用类似jsdom的东西)才能在后端运行它。
Jasmine、Jest 和其他替代方案有哪些?
Jasmine 和 Jest 有很多相似之处。如果您的 API 主要由纯函数组成,那么 Jest 和 Jasmine 都是不错的选择,可确保您的 API 按预期运行。
Jasmine 比 Jest 更快、更轻量,但功能较少。在控制台中运行测试时,Jest 更具描述性,但如果您更注重简约,您可能更喜欢 Jasmine。我们欣赏 Jest 中模拟函数的灵活性,因此对于复杂的 API,我们建议使用 Jest 而不是 Jasmine。
但是,使用 Jest 和 Jasmine 等测试框架也有几个缺点。如果您有多个团队负责应用程序的不同部分(例如:前端与后端,或原生与 Web),您可能需要为每个单独的环境编写和更新测试。此外,由于您实际上并未部署到网络,因此 API 测试环境中可能会遗漏一些细节。
出于这些原因,如果您正在模拟仍在开发中的 API,那么在本地或云端生成模拟服务器会很有用。Stoplight 有一个开源模拟服务器,它可以根据 OpenAPI 文档生成。立即开始并在获得实时数据之前设置您的 API 测试,我们希望您发现这些 API 测试最佳实践很有帮助!