protips

Logo

This site lists the protips that we shared with students during our courses

View the Project on GitHub appliedtechnology/protips

Mocking - test the code, the whole code, and nothing but the code

When you get into writing unit tests you soon will run into dependencies that your code has to external functionality and resources. It can be file-I/O, network calls, fetching from external API, or Dates.

A unit test is not a unit test if it uses these external dependencies - that would rather be an integration test. The difference between these two is more important than you first may think. Here are three things that pop into my head:

The solution to many of these problems is to mock or fake the dependency. Mocking is a minefield of terms but here I’m going to take a pragmatic approach and let it mean something that we use in our test instead of the real thing - I think that is a test fake.

Luckily the JavaScript language helps us here, as we can change and replace functions on the fly thanks to the dynamic nature of the language.

There are many libraries that will help you to set up mocking (or spying or faking or …), but we are going to do it by hand - because it’s not that hard and often quite enough.

We’re going to use two examples; dates and calling an external service with fetch

Mocking current date

Quite a lot of our code depends on dates. It can be a simple little thing like calculating someone’s current age, or days from a certain date until today, etc. The keyword here is that we want to know what the current date is to calculate these values. It might look like this:

module.exports.caluclateAge = birthYear => new Date().getFullYear() - birthYear;
console.log(caluclateAge('1972')); // prints 48

The problem is of course that if I write a test for this function - it will only work … this year:

describe('Age calulcator', () => {
  const ageCalulator = require('./ageCalculator');
  it('born 1972 = 48', () => {
    const age = ageCalulator.caluclateAge(1972);
    assert.strictEqual(age, 48);
  });
});

In this case, the fix is pretty easy… Let’s pass in the current year instead:

module.exports.caluclateAge = (year, birthYear) => year - birthYear;

// test
describe('Age calulcator', () => {
  const ageCalulator = require('./ageCalculator');
  it('born 1972 at 2020 = 48', () => {
    const age = ageCalulator.caluclateAge(2020, 1972);
    assert.strictEqual(age, 48);
  });
});

But at some point, some code will need to pass the date in… we basically just moved the problem one step up. Let’s fix it with mocking instead.

We can do that by introducing a little function to get the current year and then use it in the calculateAge:

module.exports.getYear = () => new Date().getFullYear();
module.exports.caluclateAge = birthYear => this.getYear() - birthYear;

console.log(this.caluclateAge(1972));

When we call this from the test we can now, before we call the test, add another version of the getYear-function. One that always returns the same thing so that we know how it behaves:

const assert = require('assert');
const ageCalulator = require('.');

describe('Age calulcator', () => {
  it('born 1972 at 2012 = 40 years old', () => {
    ageCalulator.getYear = () => 2012;
    const age = ageCalulator.caluclateAge(1972);
    assert.strictEqual(age, 40);
  });
});

In the case of the test we will now, instead of calling module.exports.getYear = () => new Date().getFullYear() call our replacement, mock version ageCalulator.getYear = () => 2012;. As you can see that version of getYear always returns the same thing.

If you have many case, you might want to set the mocking up in a beforeEach-block:

const assert = require('assert');
const ageCalulator = require('.');

describe('Age calulcator', () => {
  beforeEach(() => {
    ageCalulator.getYear = () => 2012;
  });
  it('born 1972 at 2012 = 40 years old', () => {
    const age = ageCalulator.caluclateAge(1972);
    assert.strictEqual(age, 40);
  });
  it('born 1977 at 2012 = 35 years old', () => {
    const age = ageCalulator.caluclateAge(1977);
    assert.strictEqual(age, 35);
  });
  it('born 1972 at 2012 = 4 years old', () => {
    const age = ageCalulator.caluclateAge(2008);
    assert.strictEqual(age, 4);
  });
});

This is especially useful if the mock-version is a bit more complicated, as it might be in the case of calling an external service.

Calling an external service

Here’s another code snippet that is not uncommon; calling an external service with fetch

const fetch = require('node-fetch');

module.exports.iNeedAHero = id =>
  fetch(`https://swapi.dev/api/people/${id}`)
    .then(res => res.json())
    .then(data => `Here's a true hero ... ${data.name}`);

this.iNeedAHero(14).then(console.log);

Ok - very simple here, but still a problem. This is slow, swap.dev status is not affecting my unit. I cannot test this without testing both fetch and the SwAPI…

Also, I’m not particularly fond of how the getting data and converting it to json is blended with my “business logic” (in this case represented by creating this string This is ${data.name}, he is ${data.height} tall).

Let’s do the same trick and break out a little helper function:

const fetch = require('node-fetch');

module.exports.fetchJSON = url => fetch(url).then(res => res.json());
module.exports.iNeedAHero = id => {
  const url = `https://swapi.dev/api/people/${id}`;
  return this.fetchJSON(url).then(data => `This is ${data.name}, he is ${data.height} tall`);
};

this.iNeedAHero(14).then(console.log);

That works the same and is a bit clearer if you ask me. But more importantly, we can now fake this in a test:

describe('Hero getter', () => {
  beforeEach(() => {
    sut.fetchJSON = () =>
      new Promise((resolve, _) =>
        resolve({
          name: 'Marcus Solo',
          height: '195',
          hair_color: 'brown',
        })
      );
  });
  it('Fetches a REAL hero', () =>
    sut
      .iNeedAHero(2345251251)
      .then(str =>
        assert.strictEqual(str, `This is Marcus Solo, he is 195 tall`)
      ));
});

Ok - quite a lot of code there, but nothing new:

But this means that we can now test all the code that is not fetching or doing networking separately from the code that does our business logic.

What do you not get with this “poor mans” version of mocking

There are tools to help us do mocking and what I’ve described above can be considered a poor mans version. But it is actually enough for many cases.

A few things that you might miss are:

Summary

Mocking is a powerful tool to ensure that we test the unit separately from its dependency. It’s not hard to set up and often leads to better-written code, where different types of functionalities are separated from each other.

(PS there’s a case against mocking as well … which I leave up to the reader to explore and make up your mind about. I’m torn between both)

The code for this post is found here