supertest vs jest-fetch-mock vs axios vs fetch-mock
HTTP Clients and Testing Strategies for JavaScript Applications
supertestjest-fetch-mockaxiosfetch-mockSimilar Packages:

HTTP Clients and Testing Strategies for JavaScript Applications

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.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
supertest18,717,00114,40149.1 kB57 days agoMIT
jest-fetch-mock1,521,36089475.9 kB73 months agoMIT
axios0109,3441.98 MB104a month agoMIT
fetch-mock01,307157 kB5a year agoMIT

HTTP Clients and Testing Strategies: axios vs fetch-mock vs jest-fetch-mock vs supertest

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.

🌐 Making HTTP Requests: Production vs Simulation

axios makes real network requests from your application code.

  • It works in browsers and Node.js without extra configuration.
  • You use it to fetch data from APIs during normal app operation.
// 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.

  • It prevents actual network traffic during tests.
  • You define rules for which URLs return which responses.
// 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.

  • It replaces the global fetch with a mock function.
  • You chain helpers to define response behavior quickly.
// 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.

  • It does not use the network; it calls the server directly.
  • You use it to verify backend routes behave correctly.
// supertest: Server integration request
import request from 'supertest';
import app from './server';

const response = await request(app).get('/users');
console.log(response.body);

πŸ§ͺ Mocking Flexibility: Granular Control vs Quick Setup

When mocking APIs, you often need to match specific conditions or just return a simple value.

fetch-mock allows complex matching logic.

  • You can match based on headers, query parameters, or custom functions.
  • This is useful when testing edge cases or authenticated routes.
// fetch-mock: Complex matching
fetchMock.get(
  (url, opts) => opts.headers['Authorization'] === 'Bearer token',
  { status: 200, body: { secure: true } }
);

jest-fetch-mock focuses on simplicity.

  • It uses sequential responses based on call order.
  • This works well for linear test cases but less so for dynamic matching.
// jest-fetch-mock: Sequential responses
fetch.mockResponses(
  [JSON.stringify({ page: 1 })],
  [JSON.stringify({ page: 2 })]
);

axios does not mock requests natively.

  • You must use adapters or external tools to mock axios specifically.
  • Many teams prefer mocking 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.

  • It runs your actual server middleware and controllers.
  • This ensures your backend code works as written without stubs.
// supertest: Real middleware execution
// Tests the actual Express route handler
await request(app).post('/login').send({ user: 'alice' });

βš™οΈ Configuration and Setup Effort

Setting up these tools varies based on your environment and goals.

axios requires minimal setup for basic use.

  • Install and import to start making requests.
  • You can create instances to manage base URLs and timeouts.
// axios: Instance configuration
const api = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 5000
});

fetch-mock needs explicit restoration after tests.

  • You must call restore() to clean up the global fetch.
  • This prevents test leakage between suites.
// fetch-mock: Cleanup
afterEach(() => {
  fetchMock.restore();
});

jest-fetch-mock integrates tightly with Jest lifecycle.

  • You enable mocks once in a setup file.
  • Resetting mocks is done via resetMocks().
// jest-fetch-mock: Jest setup
beforeEach(() => {
  fetch.resetMocks();
});

supertest requires access to the server app object.

  • You must import your Express or Koa app instance.
  • No network ports need to be opened or managed.
// supertest: App import
import app from '../src/app';
// No server.listen() needed

⚠️ Error Handling and Assertions

Verifying failures is just as important as verifying success.

axios throws errors for non-2xx status codes by default.

  • You wrap requests in try-catch blocks to handle failures.
  • The error object contains status and response details.
// axios: Error handling
try {
  await axios.get('/users');
} catch (error) {
  console.log(error.response.status);
}

fetch-mock lets you simulate error responses.

  • You can configure it to return 500 status or network errors.
  • This helps test your app's error UI.
// fetch-mock: Simulating errors
fetchMock.get('/users', 500);

jest-fetch-mock simulates errors via rejection.

  • You use mockReject to trigger catch blocks.
  • This mimics network failures or aborted requests.
// jest-fetch-mock: Simulating rejection
fetch.mockRejectOnce(new Error('Network Error'));

supertest provides built-in assertion methods.

  • You chain .expect() to verify status and body.
  • Tests fail automatically if expectations are not met.
// supertest: Assertions
await request(app)
  .get('/users')
  .expect(200)
  .expect('Content-Type', /json/);

πŸ“Š Summary Table

Featureaxiosfetch-mockjest-fetch-mocksupertest
Primary UseProduction RequestsMocking fetchMocking fetch (Jest)Server Testing
EnvironmentBrowser & NodeTest RunnersJest OnlyNode.js Servers
NetworkReal HTTPSimulatedSimulatedDirect Call
SetupImport & UseMock & RestoreEnable & ResetImport App
Best ForApp Data FetchingComplex MocksSimple Jest MocksBackend Integration

πŸ’‘ Final Recommendation

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.

How to Choose: supertest vs jest-fetch-mock vs axios vs fetch-mock

  • supertest:

    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.

  • jest-fetch-mock:

    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.

  • axios:

    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.

  • fetch-mock:

    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.

README for supertest

supertest

build status code coverage code style styled with prettier made with lass license

HTTP assertions made easy via superagent. Maintained for Forward Email and Lad.

About

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.

Getting Started

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');

Example

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();
  });

API

You may use any superagent methods, including .write(), .pipe() etc and perform assertions in the .end() callback for lower-level needs.

.expect(status[, fn])

Assert response status code.

.expect(status, body[, fn])

Assert response status code and body.

.expect(body[, fn])

Assert response body text with a string, regular expression, or parsed body object.

.expect(field, value[, fn])

Assert header field value with a string or regular expression.

.expect(function(res) {})

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");
}

.end(fn)

Perform the request and invoke fn(err, res).

Cookies

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', {/* ... */})

Cookie assertions

Functions and methods are chainable.

cookies([secret], [asserts])

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.

.set(expects, [assert])

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.

.reset(expects, [assert])

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.

.new(expects, [assert])

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.

.renew(expects, [assert])

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 below
    • options.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.

.contain(expects, [assert])

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.

.not(method, expects)

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.

Notes

Inspired by api-easy minus vows coupling.

License

MIT