YesNo HTTP Testing for Node.js
raw JSON →YesNo is an HTTP testing library for Node.js designed to intercept, mock, and record outgoing HTTP requests. Utilizing the `Mitm` library, it operates at a low level within the Node.js process, allowing for the inspection and manipulation of actual HTTP traffic generated by an application, rather than simply mocking higher-level request library calls. This approach enables more accurate and robust testing of an application's real-world network behavior. The current version, 0.0.7, is explicitly noted as a beta release, indicating that its API is subject to change and may undergo breaking modifications before reaching its first stable major release. The project is actively maintained, focusing on reaching this 1.0.0 milestone. Key features include spying on requests, easily mocking responses, and recording interactions for future use or to generate test data.
Common errors
error Error: Mitm is already enabled! ↓
yesno.restore() is called after every test or test suite where yesno.spy() is used, typically within an afterEach or afterAll hook in your test runner. error TypeError: Cannot read properties of undefined (reading 'response') or requests not being intercepted. ↓
yesno.spy() is called *before* the code under test makes its HTTP requests, and yesno.restore() is called *after* all asynchronous operations related to the test are finished, often by ensuring the test case is async/await and awaits all network operations. Place yesno.spy() in beforeEach and yesno.restore() in afterEach for most unit test setups. Warnings
breaking YesNo is currently in beta (v0.0.7), and its API is explicitly noted as subject to change. Developers should expect potential breaking changes in minor or even patch versions before the first major stable release (1.0.0). ↓
gotcha YesNo uses `Mitm` to intercept *all* outgoing network TCP and HTTP connections from the Node.js process. This can include unexpected traffic from other libraries or even Node.js internals, potentially interfering with other network-related test setups or debugging. ↓
gotcha When intercepting HTTPS traffic, `Mitm` (and thus YesNo) might encounter issues with self-signed certificates or custom Certificate Authorities, leading to `ERR_CERT_AUTHORITY_INVALID` or similar trust errors. ↓
Install
npm install yesno-http yarn add yesno-http pnpm add yesno-http Imports
- yesno wrong
const yesno = require('yesno-http');correctimport { yesno } from 'yesno-http'; - ISerializedHttp wrong
import { ISerializedHttp } from 'yesno-http';correctimport type { ISerializedHttp } from 'yesno-http'; - yesno.spy
yesno.spy(); - yesno.restore
yesno.restore();
Quickstart
const { yesno } = require('yesno-http');
const { expect } = require('chai'); // Assuming chai for assertions
const http = require('http'); // For a simple HTTP request example
// Simulate a simple API call function
const myApi = {
getUsers: async () => {
return new Promise((resolve, reject) => {
http.get('http://api.example.com/users', (res) => {
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => {
try {
resolve(JSON.parse(data));
} catch (e) {
reject(e);
}
});
}).on('error', (err) => {
reject(err);
});
});
}
};
describe('my-api with YesNo', () => {
beforeEach(() => {
yesno.spy(); // Intercept requests before each test
});
afterEach(() => {
yesno.restore(); // Clean up after each test to prevent interference
});
it('should get users and intercept the request', async () => {
// Configure a mock response for 'api.example.com/users'
yesno.mock([
{
url: 'http://api.example.com/users',
response: {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: { users: [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }] }
}
}
]);
const users = await myApi.getUsers();
const interceptedRequests = yesno.intercepted(); // Get the intercepted requests
expect(interceptedRequests).to.have.lengthOf(1);
expect(interceptedRequests[0]).to.have.nested.property('url', 'http://api.example.com/users');
expect(users).to.deep.eql([{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]);
expect(interceptedRequests[0].response.statusCode).to.equal(200);
});
it('should allow filtering intercepted requests', async () => {
yesno.mock([
{ url: 'http://api.example.com/posts', response: { statusCode: 200, body: [] } },
{ url: 'http://api.example.com/comments', response: { statusCode: 200, body: [] } }
]);
await Promise.all([
new Promise((resolve) => http.get('http://api.example.com/posts', resolve)),
new Promise((resolve) => http.get('http://api.example.com/comments', resolve))
]);
const postsRequests = yesno.intercepted(/posts/);
expect(postsRequests).to.have.lengthOf(1);
expect(postsRequests[0].url).to.include('/posts');
});
});