axios is a promise-based HTTP client used for making actual network requests in both browser and Node.js environments. fetch-mock and jest-fetch-mock are testing utilities designed to mock the fetch API, allowing developers to simulate network responses without hitting real servers. supertest is an assertion library specifically built for testing Node.js HTTP servers, enabling integration tests that verify server behavior end-to-end. Together, these tools cover the full spectrum of HTTP interaction β from production client requests to unit mocking and server integration testing.
Building reliable JavaScript applications requires handling HTTP requests in production and verifying them in tests. axios serves as a production client, while fetch-mock and jest-fetch-mock simulate responses for unit tests. supertest takes a different approach by testing the server itself. Let's break down how each tool handles HTTP interactions.
axios makes real network requests from your application code.
// axios: Real production request
import axios from 'axios';
const response = await axios.get('https://api.example.com/users');
console.log(response.data);
fetch-mock intercepts fetch calls to return simulated data.
// fetch-mock: Mocked request setup
import fetchMock from 'fetch-mock';
fetchMock.get('https://api.example.com/users', { users: [] });
const response = await fetch('https://api.example.com/users');
const data = await response.json();
jest-fetch-mock also intercepts fetch but is tuned for Jest.
fetch with a mock function.// jest-fetch-mock: Mocked request setup
import fetch from 'jest-fetch-mock';
fetch.mockResponseOnce(JSON.stringify({ users: [] }));
const response = await fetch('https://api.example.com/users');
const data = await response.json();
supertest sends requests to a Node.js server instance.
// supertest: Server integration request
import request from 'supertest';
import app from './server';
const response = await request(app).get('/users');
console.log(response.body);
When mocking APIs, you often need to match specific conditions or just return a simple value.
fetch-mock allows complex matching logic.
// fetch-mock: Complex matching
fetchMock.get(
(url, opts) => opts.headers['Authorization'] === 'Bearer token',
{ status: 200, body: { secure: true } }
);
jest-fetch-mock focuses on simplicity.
// jest-fetch-mock: Sequential responses
fetch.mockResponses(
[JSON.stringify({ page: 1 })],
[JSON.stringify({ page: 2 })]
);
axios does not mock requests natively.
axios specifically.fetch instead if using axios in tests.// axios: Requires adapter for mocking
// No built-in mock method; typically mocked via jest.mock('axios')
supertest does not mock; it tests real logic.
// supertest: Real middleware execution
// Tests the actual Express route handler
await request(app).post('/login').send({ user: 'alice' });
Setting up these tools varies based on your environment and goals.
axios requires minimal setup for basic use.
// axios: Instance configuration
const api = axios.create({
baseURL: 'https://api.example.com',
timeout: 5000
});
fetch-mock needs explicit restoration after tests.
restore() to clean up the global fetch.// fetch-mock: Cleanup
afterEach(() => {
fetchMock.restore();
});
jest-fetch-mock integrates tightly with Jest lifecycle.
resetMocks().// jest-fetch-mock: Jest setup
beforeEach(() => {
fetch.resetMocks();
});
supertest requires access to the server app object.
// supertest: App import
import app from '../src/app';
// No server.listen() needed
Verifying failures is just as important as verifying success.
axios throws errors for non-2xx status codes by default.
// axios: Error handling
try {
await axios.get('/users');
} catch (error) {
console.log(error.response.status);
}
fetch-mock lets you simulate error responses.
// fetch-mock: Simulating errors
fetchMock.get('/users', 500);
jest-fetch-mock simulates errors via rejection.
mockReject to trigger catch blocks.// jest-fetch-mock: Simulating rejection
fetch.mockRejectOnce(new Error('Network Error'));
supertest provides built-in assertion methods.
.expect() to verify status and body.// supertest: Assertions
await request(app)
.get('/users')
.expect(200)
.expect('Content-Type', /json/);
| Feature | axios | fetch-mock | jest-fetch-mock | supertest |
|---|---|---|---|---|
| Primary Use | Production Requests | Mocking fetch | Mocking fetch (Jest) | Server Testing |
| Environment | Browser & Node | Test Runners | Jest Only | Node.js Servers |
| Network | Real HTTP | Simulated | Simulated | Direct Call |
| Setup | Import & Use | Mock & Restore | Enable & Reset | Import App |
| Best For | App Data Fetching | Complex Mocks | Simple Jest Mocks | Backend Integration |
These tools solve different problems in the development lifecycle. Use axios for your actual application code when you need to fetch data from APIs. Reach for fetch-mock if you need detailed control over mocking network requests in various test environments. Pick jest-fetch-mock if you want the fastest setup for mocking fetch within Jest tests. Finally, rely on supertest when you need to verify your Node.js backend servers work correctly end-to-end.
Key Takeaway: Do not use testing tools like supertest or fetch-mock in production code. Keep your production dependencies lean with axios or native fetch, and reserve the mocking and assertion libraries for your test suites.
Choose supertest when you need to test Node.js HTTP servers directly without running a live network server. It allows you to assert status codes, headers, and response bodies against Express or Koa applications efficiently. Use this library for backend integration tests rather than frontend client mocking.
Choose jest-fetch-mock if you are working exclusively within a Jest environment and want a simple setup for mocking fetch. It provides convenient helpers like mockResponse that reduce boilerplate compared to generic mocking tools. This package is optimal for teams prioritizing quick configuration over advanced matching features.
Choose axios when you need a robust HTTP client for production application code that supports interceptors, request cancellation, and automatic JSON transformation. It is ideal for frontend applications or Node.js services that require consistent behavior across different environments. Use this package for actual data fetching rather than testing scenarios.
Choose fetch-mock if you need a powerful mocking library that works across various test runners like Jest, Mocha, or Jasmine. It offers granular control over matching URLs, methods, and headers, making it suitable for complex testing scenarios. This tool is best when you need to mock the global fetch API with high flexibility.
HTTP assertions made easy via superagent. Maintained for Forward Email and Lad.
The motivation with this module is to provide a high-level abstraction for testing HTTP, while still allowing you to drop down to the lower-level API provided by superagent.
Install supertest as an npm module and save it to your package.json file as a development dependency:
npm install supertest --save-dev
Once installed it can now be referenced by simply calling require('supertest');
You may pass an http.Server, or a Function to request() - if the server is not
already listening for connections then it is bound to an ephemeral port for you so
there is no need to keep track of ports.
supertest works with any test framework, here is an example without using any test framework at all:
const request = require('supertest');
const express = require('express');
const app = express();
app.get('/user', function(req, res) {
res.status(200).json({ name: 'john' });
});
request(app)
.get('/user')
.expect('Content-Type', /json/)
.expect('Content-Length', '15')
.expect(200)
.end(function(err, res) {
if (err) throw err;
});
To enable http2 protocol, simply append an options to request or request.agent:
const request = require('supertest');
const express = require('express');
const app = express();
app.get('/user', function(req, res) {
res.status(200).json({ name: 'john' });
});
request(app, { http2: true })
.get('/user')
.expect('Content-Type', /json/)
.expect('Content-Length', '15')
.expect(200)
.end(function(err, res) {
if (err) throw err;
});
request.agent(app, { http2: true })
.get('/user')
.expect('Content-Type', /json/)
.expect('Content-Length', '15')
.expect(200)
.end(function(err, res) {
if (err) throw err;
});
Here's an example with mocha, note how you can pass done straight to any of the .expect() calls:
describe('GET /user', function() {
it('responds with json', function(done) {
request(app)
.get('/user')
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(200, done);
});
});
You can use auth method to pass HTTP username and password in the same way as in the superagent:
describe('GET /user', function() {
it('responds with json', function(done) {
request(app)
.get('/user')
.auth('username', 'password')
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(200, done);
});
});
One thing to note with the above statement is that superagent now sends any HTTP
error (anything other than a 2XX response code) to the callback as the first argument if
you do not add a status code expect (i.e. .expect(302)).
If you are using the .end() method .expect() assertions that fail will
not throw - they will return the assertion as an error to the .end() callback. In
order to fail the test case, you will need to rethrow or pass err to done(), as follows:
describe('POST /users', function() {
it('responds with json', function(done) {
request(app)
.post('/users')
.send({name: 'john'})
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(200)
.end(function(err, res) {
if (err) return done(err);
return done();
});
});
});
You can also use promises:
describe('GET /users', function() {
it('responds with json', function() {
return request(app)
.get('/users')
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(200)
.then(response => {
expect(response.body.email).toEqual('foo@bar.com');
})
});
});
Or async/await syntax:
describe('GET /users', function() {
it('responds with json', async function() {
const response = await request(app)
.get('/users')
.set('Accept', 'application/json')
expect(response.headers["content-type"]).toMatch(/json/);
expect(response.status).toEqual(200);
expect(response.body.email).toEqual('foo@bar.com');
});
});
Expectations are run in the order of definition. This characteristic can be used to modify the response body or headers before executing an assertion.
describe('POST /user', function() {
it('user.name should be an case-insensitive match for "john"', function(done) {
request(app)
.post('/user')
.send('name=john') // x-www-form-urlencoded upload
.set('Accept', 'application/json')
.expect(function(res) {
res.body.id = 'some fixed id';
res.body.name = res.body.name.toLowerCase();
})
.expect(200, {
id: 'some fixed id',
name: 'john'
}, done);
});
});
Anything you can do with superagent, you can do with supertest - for example multipart file uploads!
request(app)
.post('/')
.field('name', 'my awesome avatar')
.field('complex_object', '{"attribute": "value"}', {contentType: 'application/json'})
.attach('avatar', 'test/fixtures/avatar.jpg')
...
Passing the app or url each time is not necessary, if you're testing
the same host you may simply re-assign the request variable with the
initialization app or url, a new Test is created per request.VERB() call.
request = request('http://localhost:5555');
request.get('/').expect(200, function(err){
console.log(err);
});
request.get('/').expect('heya', function(err){
console.log(err);
});
Here's an example with mocha that shows how to persist a request and its cookies:
const request = require('supertest');
const should = require('should');
const express = require('express');
const cookieParser = require('cookie-parser');
describe('request.agent(app)', function() {
const app = express();
app.use(cookieParser());
app.get('/', function(req, res) {
res.cookie('cookie', 'hey');
res.send();
});
app.get('/return', function(req, res) {
if (req.cookies.cookie) res.send(req.cookies.cookie);
else res.send(':(')
});
const agent = request.agent(app);
it('should save cookies', function(done) {
agent
.get('/')
.expect('set-cookie', 'cookie=hey; Path=/', done);
});
it('should send cookies', function(done) {
agent
.get('/return')
.expect('hey', done);
});
});
There is another example that is introduced by the file agency.js
Here is an example where 2 cookies are set on the request.
agent(app)
.get('/api/content')
.set('Cookie', ['nameOne=valueOne;nameTwo=valueTwo'])
.send()
.expect(200)
.end((err, res) => {
if (err) {
return done(err);
}
expect(res.text).to.be.equal('hey');
return done();
});
You may use any superagent methods,
including .write(), .pipe() etc and perform assertions in the .end() callback
for lower-level needs.
Assert response status code.
Assert response status code and body.
Assert response body text with a string, regular expression, or
parsed body object.
Assert header field value with a string or regular expression.
Pass a custom assertion function. It'll be given the response object to check. If the check fails, throw an error.
request(app)
.get('/')
.expect(hasPreviousAndNextKeys)
.end(done);
function hasPreviousAndNextKeys(res) {
if (!('next' in res.body)) throw new Error("missing next key");
if (!('prev' in res.body)) throw new Error("missing prev key");
}
Perform the request and invoke fn(err, res).
Here is an example of using the set and not cookie assertions:
// setup super-test
const request = require('supertest');
const express = require('express');
const cookies = request.cookies;
// setup express test service
const app = express();
app.get('/users', function(req, res) {
res.cookie('alpha', 'one', { domain: 'domain.com', path: '/', httpOnly: true });
res.send(200, { name: 'tobi' });
});
// test request to service
request(app)
.get('/users')
.expect('Content-Type', /json/)
.expect('Content-Length', '15')
.expect(200)
// assert 'alpha' cookie is set with domain, path, and httpOnly options
.expect(cookies.set({ name: 'alpha', options: ['domain', 'path', 'httponly'] }))
// assert 'bravo' cookie is NOT set
.expect(cookies.not('set', { name: 'bravo' }))
.end(function(err, res) {
if (err) {
throw err;
}
});
It is also possible to chain assertions:
cookies.set({/* ... */}).not('set', {/* ... */})
Functions and methods are chainable.
Get assertion function for super-test .expect() method.
Arguments
secret - String or array of strings. Cookie signature secrets.asserts(req, res) - Function or array of functions. Failed custom assertions should throw.Assert that cookie and options are set.
Arguments
expects - Object or array of objects.
name - String name of cookie.options - Optional array of options.assert - Optional boolean "assert true" modifier. Default: true.Assert that cookie is set and was already set (in request headers).
Arguments
expects - Object or array of objects.
name - String name of cookie.assert - Optional boolean "assert true" modifier. Default: true.Assert that cookie is set and was NOT already set (NOT in request headers).
Arguments
expects - Object or array of objects.
name - String name of cookie.assert - Optional boolean "assert true" modifier. Default: true.Assert that cookie is set with a strictly greater expires or max-age than the given value.
Arguments
expects - Object or array of objects.
name - String name of cookie.options - Object of options. use one of two options belowoptions.expires - String UTC expiration for original cookie (in request headers).options.max-age - Integer ttl in seconds for original cookie (in request headers).assert - Optional boolean "assert true" modifier. Default: true.Assert that cookie is set with value and contains options.
Requires cookies(secret) initialization if cookie is signed.
Arguments
expects - Object or array of objects.
name - String name of cookie.value - Optional string unsigned value of cookie.options - Optional object of options.options.domain - Optional string domain.options.path - Optional string path.options.expires - Optional string UTC expiration.options.max-age - Optional integer ttl, in seconds.options.secure - Optional boolean secure flag.options.httponly - Optional boolean httpOnly flag.assert - Optional boolean "assert true" modifier. Default: true.Call any cookies assertion method with "assert true" modifier set to false.
Syntactic sugar.
Arguments
method - String method name. Arguments of method name apply in expects.expects - Object or array of objects.
name - String name of cookie.value - Optional string unsigned value of cookie.options - Optional object of options.Inspired by api-easy minus vows coupling.
MIT