Register to get access to free programming courses with interactive exercises

Testing HTTP Requests JS: Advanced Testing

Dependency inversion is an extremely powerful technique that works not only with functions, but also with objects. Let's take a deeper look at it using HTTP requests as an example, and get acquainted with the notion of a stub.

Suppose we have a function that parses an organization's private repositories on GitHub and returns the ones that are forks (i.e., split off from the main directory):

// Library for working with GitHub API
import Octokit from '@octokit/rest';

const getPrivateForksNames = async (username) => {
  const client = new Octokit();
  // The client makes a request to a GitHub and returns a list of the specified organization's private repositories
  // The data is stored in the data property of the return response
  const { data } = await client.repos
    .listForOrg({
      username,
      type: 'private',
    });
  // We leave only the names of the forks
  return data.filter(repo => repo.fork).map(repo => repo.name);
};

Let's test it. What do we want from this feature? First of all, we need to make sure that it works correctly, i.e., that it returns an array of private forks. An ideal test would look like this:

test('getPrivateForksNames', async () => {
  const privateForks = await getPrivateForksNames('hexlet');
  expect(privateForks).toEqual([/* an array of names that we expect to see  */]);
});

Unfortunately, it's not that simple. An HTTP request is made inside the function. Let's see what kind of problems this can cause:

  1. An unstable network can slow down test execution and lead to phantom errors. The tests will sometimes past, and sometimes not.
  2. Services like github.com have limits on requests per second, per hour, per day, and so on. It's guaranteed that the tests will begin to stall at these limits. Moreover, there's a chance that the machine that the requests come from will be blocked.
  3. The actual data on GitHub isn't static, it can and probably will change, which again will lead to errors and your tests will need to be fixed.

In this example, the HTTP request is seen as a hindrance to testing our underlying logic. We trust github.com and its @octokit/rest library, which means we don't need to check it works correctly (or you'll go nuts if you don't trust anyone).

In the previous lesson, we learned about several ways out of this situation, this is one instance in which we can apply one of them.

Dependency inversion

In order to use dependency inversion, we add the Octokit client itself as the second argument for the function. This will allow you to substitute it in the tests:

import Octokit from '@octokit/rest';

// The library is passed from the outside and can substituted
const getPrivateForksNames = async (username, client = new Octokit()) => {
  // ...
};

We have to implement a fake client, which behaves similarly to the real Octokit except that it doesn't make any network requests. We also need to describe the specific data that the listForOrg call will return. Only then we can test to see that the getPrivateForksNames() function works correctly.

// The structure of this class only describes the part
// needed to call await client.repos.listForOrg(...)
class OctokitFake {
  // Here we define the desired data that is to be returned in the test
  constructor(data) {
    this.data = data;
  }

  repos = {
    listForOrg: () => {
      // the structure of the return must match the real client
      // Only then will it be possible to transparently replace the real client with a fake one
      return Promise.resolve({ data: this.data }); // because the method is asynchronous
    },
  }
}

And the test itself using this client:

import OctokitFake from '/OctokitFake.js';

test('getPrivateForksNames', async () => {
  const data = /* the response from GitHub, which we want to verify */;
  const client = new OctokitFake(data);
  // Internally, a "query" is performed that returns the data generated above
  const username = /* GitHub username */;
  const privateForks = await getPrivateForksNames(username, client);
  expect(privateForks).toEqual(/* which we expect based on what listForOrg returned */);
});

When it comes to testing for such fake objects (or functions), we have a special name for them - stubs. A stub replaces a real object or function, avoiding side effects or making the code deterministic. Stubs aren't used to test anything, they only allow you to isolate the part that interferes with testing the underlying logic.

Banning HTTP requests

Another way to avoid HTTP requests from tests is to disable them in tests. In future lessons, we'll get acquainted with the Nock library, which has a method for banning any HTTP requests from the code: nock.disableNetConnect(). We recommend calling it at the beginning of the test file. In addition, it helps to see which pages are being queried by third-party libraries. This is what the output looks like after disabling external connections (assuming that no query spoofing was performed):

    HttpError: request to https://api.github.com/orgs/hexlet/repos?type=private failed, reason: Nock: Disallowed net connect for "api.github.com:443/orgs/hexlet/repos?type=private"

Are there any more questions? Ask them in the Discussion section.

The Hexlet support team or other students will answer you.

About Hexlet learning process

For full access to the course you need a professional subscription.

A professional subscription will give you full access to all Hexlet courses, projects and lifetime access to the theory of lessons learned. You can cancel your subscription at any time.

Get access
130
courses
1000
exercises
2000+
hours of theory
3200
tests

Sign up

Programming courses for beginners and experienced developers. Start training for free

  • 130 courses, 2000+ hours of theory
  • 1000 practical tasks in a browser
  • 360 000 students
By sending this form, you agree to our Personal Policy and Service Conditions

Our graduates work in companies:

<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.bookmate">Bookmate</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.healthsamurai">Healthsamurai</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.dualboot">Dualboot</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.abbyy">Abbyy</span>
Suggested learning programs
profession
Development of front-end components for web applications
10 months
from scratch
Start at any time

Use Hexlet to the fullest extent!

  • Ask questions about the lesson
  • Test your knowledge in quizzes
  • Practice in your browser
  • Track your progress

Sign up or sign in

By sending this form, you agree to our Personal Policy and Service Conditions
Toto Image

Ask questions if you want to discuss a theory or an exercise. Hexlet Support Team and experienced community members can help find answers and solve a problem.