
在Node.js环境中,进行外部REST API调用是常见的操作。为了提高代码的可维护性和可测试性,通常会将原生的HTTP请求逻辑封装起来。以下是我们将要测试的函数模块:
// crud.js (或类似文件)
const https = require('https'); // 假设这里是 https 模块
function getOptions(req, url) {
// 实际项目中这里可能根据 req 和 url 生成请求选项
return url; // 简化处理,直接返回 url
}
function handleResponse(response, callback, errorCallback) {
let rawData = '';
response.on('data', (chunk) => {
rawData += chunk;
});
response.on('end', () => {
if (response.statusCode >= 200 && response.statusCode < 300) {
callback(checkJSONResponse(rawData));
} else if (errorCallback) {
errorCallback(rawData);
}
});
}
function checkJSONResponse(rawData) {
if (typeof rawData === 'object') {
return rawData; // 如果已经是对象,直接返回
}
let data = {};
if (rawData.length > 0) {
try {
data = JSON.parse(rawData);
} catch (e) {
console.log('Response is not JSON.');
if (e) {
console.log(e);
}
data = {}; // 解析失败返回空对象
}
}
return data;
}
module.exports.get = function(req, url, callback, errorCallback) {
https.get(getOptions(req, url), (response) => {
handleResponse(response, callback, errorCallback);
}).on('error', (e) => {
console.error('MYAPP-GET Request.', e);
if (errorCallback) {
errorCallback(e);
}
});
};该模块的核心是 module.exports.get 函数,它负责发起HTTPS GET请求,并通过回调函数 callback 和 errorCallback 处理成功和失败的响应。handleResponse 处理HTTP响应流,而 checkJSONResponse 则尝试将响应数据解析为JSON。
对于上述异步操作和外部依赖(如 https 模块)的代码,编写单元测试至关重要。
主要挑战在于:
Jest是一个流行的JavaScript测试框架,它提供了强大的断言库、模拟(mocking)功能和异步测试支持。
首先,确保你的项目中安装了Jest:
npm install --save-dev jest
在你的测试文件中(例如 crud.test.js),你需要引入待测试的模块和 https 模块(以便进行模拟)。
// crud.test.js
const crud = require('./crud'); // 假设你的封装函数在 crud.js 中
const https = require('https'); // 引入 https 模块,用于模拟这是单元测试的关键一步。我们不希望测试真正发起网络请求,因此需要模拟 https.get 方法。Jest提供了 jest.mock() 和 mockImplementation() 来实现这一点。
// 在测试文件的顶部
jest.mock('https'); // 模拟整个 https 模块当 https 模块被模拟后,https.get 将不再是其原始实现。我们可以在每个测试用例中定义其模拟行为。
我们将针对不同的场景编写测试用例。
在这个场景中,我们模拟 https.get 返回一个成功的HTTP响应,其中包含有效的JSON数据。
describe('crud.get', () => {
let mockCallback;
let mockErrorCallback;
beforeEach(() => {
// 在每个测试用例前重置模拟函数
mockCallback = jest.fn();
mockErrorCallback = jest.fn();
// 清除 https.get 的所有模拟,确保每个测试用例都是独立的
https.get.mockClear();
});
it('should call callback with parsed JSON data on successful 2xx response', (done) => {
const mockUrl = 'https://example.com/api/data';
const mockResponseData = { id: 1, name: 'Test Data' };
const mockRawData = JSON.stringify(mockResponseData);
// 模拟 response 对象,包括 statusCode 和 on 方法
const mockResponse = {
statusCode: 200,
on: jest.fn((event, handler) => {
if (event === 'data') {
handler(mockRawData); // 模拟数据块
} else if (event === 'end') {
handler(); // 模拟响应结束
}
}),
};
// 模拟 https.get 方法
https.get.mockImplementation((options, responseCallback) => {
responseCallback(mockResponse); // 立即调用响应回调
return {
on: jest.fn(), // 模拟 .on('error'),避免未定义错误
};
});
crud.get(null, mockUrl, mockCallback, mockErrorCallback);
// 使用 setTimeout 或 process.nextTick 确保异步回调被执行
// 或者在 Jest 11+ 中使用 done 回调
process.nextTick(() => {
expect(https.get).toHaveBeenCalledTimes(1);
expect(https.get).toHaveBeenCalledWith(mockUrl, expect.any(Function)); // 检查URL和回调
expect(mockResponse.on).toHaveBeenCalledWith('data', expect.any(Function));
expect(mockResponse.on).toHaveBeenCalledWith('end', expect.any(Function));
expect(mockCallback).toHaveBeenCalledTimes(1);
expect(mockCallback).toHaveBeenCalledWith(mockResponseData);
expect(mockErrorCallback).not.toHaveBeenCalled();
done(); // 标记异步测试完成
});
});
// 场景一变种:成功获取空JSON响应 (例如 {})
it('should call callback with empty object if response is empty JSON', (done) => {
const mockUrl = 'https://example.com/api/empty';
const mockRawData = '{}';
const mockResponse = {
statusCode: 200,
on: jest.fn((event, handler) => {
if (event === 'data') { handler(mockRawData); }
else if (event === 'end') { handler(); }
}),
};
https.get.mockImplementation((options, responseCallback) => {
responseCallback(mockResponse);
return { on: jest.fn() };
});
crud.get(null, mockUrl, mockCallback, mockErrorCallback);
process.nextTick(() => {
expect(mockCallback).toHaveBeenCalledWith({});
done();
});
});
// 场景一变种:成功获取非JSON响应
it('should call callback with empty object if response is non-JSON', (done) => {
const mockUrl = 'https://example.com/api/text';
const mockRawData = 'This is plain text.';
const mockResponse = {
statusCode: 200,
on: jest.fn((event, handler) => {
if (event === 'data') { handler(mockRawData); }
else if (event === 'end') { handler(); }
}),
};
https.get.mockImplementation((options, responseCallback) => {
responseCallback(mockResponse);
return { on: jest.fn() };
});
crud.get(null, mockUrl, mockCallback, mockErrorCallback);
process.nextTick(() => {
expect(mockCallback).toHaveBeenCalledWith({}); // checkJSONResponse 会返回空对象
done();
});
});
});代码解析:
当HTTP请求返回非2xx状态码(如404 Not Found, 500 Internal Server Error)时,errorCallback 应该被调用。
describe('crud.get', () => {
let mockCallback;
let mockErrorCallback;
beforeEach(() => {
mockCallback = jest.fn();
mockErrorCallback = jest.fn();
https.get.mockClear();
});
it('should call errorCallback on non-2xx response status', (done) => {
const mockUrl = 'https://example.com/api/notfound';
const mockRawErrorData = 'Not Found';
const mockResponse = {
statusCode: 404,
on: jest.fn((event, handler) => {
if (event === 'data') { handler(mockRawErrorData); }
else if (event === 'end') { handler(); }
}),
};
https.get.mockImplementation((options, responseCallback) => {
responseCallback(mockResponse);
return { on: jest.fn() };
});
crud.get(null, mockUrl, mockCallback, mockErrorCallback);
process.nextTick(() => {
expect(mockCallback).not.toHaveBeenCalled();
expect(mockErrorCallback).toHaveBeenCalledTimes(1);
expect(mockErrorCallback).toHaveBeenCalledWith(mockRawErrorData);
done();
});
});
});当 https.get 本身发生网络错误(例如DNS解析失败、连接超时)时,其返回的EventEmitter会触发 error 事件。
describe('crud.get', () => {
let mockCallback;
let mockErrorCallback;
beforeEach(() => {
mockCallback = jest.fn();
mockErrorCallback = jest.fn();
https.get.mockClear();
});
it('should call errorCallback when https.get emits an error', (done) => {
const mockUrl = 'https://example.com/api/error';
const mockError = new Error('Network error occurred');
// 模拟 https.get 返回一个 EventEmitter,并立即触发 'error' 事件
https.get.mockImplementation((options, responseCallback) => {
// 返回一个模拟的 EventEmitter
const reqEmitter = {
on: jest.fn((event, handler) => {
if (event === 'error') {
// 在下一个事件循环中触发错误,模拟真实异步行为
process.nextTick(() => handler(mockError));
}
}),
};
return reqEmitter;
});
crud.get(null, mockUrl, mockCallback, mockErrorCallback);
// 等待异步错误处理完成
process.nextTick(() => {
expect(mockCallback).not.toHaveBeenCalled();
expect(mockErrorCallback).toHaveBeenCalledTimes(1);
expect(mockErrorCallback).toHaveBeenCalledWith(mockError);
done();
});
});
});重要注意事项:
通过本文,我们学习了如何使用Jest框架为Node.js中封装的REST GET请求函数编写全面的单元测试。关键步骤包括:
掌握这些技术,将有助于你编写出更健壮、可维护且易于测试的Node.js异步代码。
以上就是深入理解与实践:使用Jest测试Node.js REST GET请求封装函数的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号