• 15 hours
  • Easy

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 1/19/24

Test that a function does what it says

The importance of testing

Dr. Zoidberg thinks your tests are bad
Testing is important

About a year ago, I was brought into a start-up to help them finish development in time for their deployment date. This company's product dealt with large numbers of small transactions, and so accuracy was essential.

I was working on the front end, but I immediately noticed an issue: the calculations weren't adding up. We would put in a few hundred transactions, and the totals would be off by tens of euros. Something was going seriously wrong.

This project was large, complex, and very ambitious (the front end alone had more than 600,000 lines of code!), but this start-up had not implemented any testing framework. Tracking down the problem took us over a week with mounting frustration and panic. There were a quarter million euros in play, and that was just for one client.

This story ended well, but it took a whole week of digging for one error, which would have been caught if there had been decent testing. We could have lost enough money that the company would have gone bust.

What is testing?

Well, that depends! There are three main types.

Unit tests

Unit testing checks individual units (generally single functions or classes) by providing them with input and making sure they generate the expected output.

Generally, each unit is tested for a simple case, and then for one or more edge cases.

For example, if you take a couple of functions from a previous chapter:

const getWordCount = (stringToTest) => {
  const wordArray = stringToTest.split(' ');
  return wordArray.length;
}

const getLetterCount = (stringToTest) => {
  const wordArray = stringToTest.split(' ');
  let totalLetters = 0;
  for (let word of wordArray) {
    word.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g, "");
    totalLetters += word.length;
  }
  return totalLetters;
}

Which cases would you check for each function here?

  • getWordCount  — You  would check a string where you know the number of words (simple case), then perhaps an empty string, and a string containing only spaces (edge cases).

  • getLetterCount  — You would check string where you know the number of letters (simple case); then try a string with only punctuation (edge case).

You could write these tests as helper code:

const testSimpleWordCount = () => {
    const testString = 'I have four words!';
    if (getWordCount(testString) !== 4) {
        console.error('Simple getWordCount failed!');
    }
}

const testEdgeWordCount = () => {
    const testString = '             ';
    if (getWordCount(testString) !== 0) {
        console.error('Edge getWordCount failed!');
    }
}

const testSimpleLetterCount = () => {
    const testString = 'I have twenty one letters!';
    if (getLetterCount(testString) !== 21) {
        console.error('Simple getLetterCount failed!');
    }
}

const testEdgeLetterCount = () => {
    const testString = '")(&;//!!';
    if (getLetterCount(testString) !== 0) {
        console.error('Edge getLetterCount failed!');
    }
}

These are simple tests, and can be OK for quick checks, but it is generally better to use a testing framework.

Testing frameworks and libraries allow you to write whole test suites to test your code automatically, using special functions and syntax. Here is what the first two tests above might look like in certain frameworks:

describe('getWordCount()', function() {
    it('should find four words', function() {
        expect(getWordCount('I have four words!').to.equal(4));
    });
    it('should find no words', function() {
        expect(getWordCount('      ').to.equal(0));
    });
});

Unit tests generally make up between 60% and 80% of overall tests in JavaScript projects. Next up, we have:

Integration tests

Integration tests are important
Integration tests are important

Integration tests check multiple functions or classes to make sure that they work together as they are supposed to. The above image shows what can happen when the individual units work properly (both drawers open properly in isolation), but their integration in the surrounding system causes a malfunction.

Functional tests

Functional tests, also known as end-to-end (E2E) tests, check whole scenarios in context. For example, a user signs in to your app, opens their notifications, and marks them all as read. These tests also check any external resources your project may use, like a third-party payment system, for example.

Let's recap!

In this chapter, you were introduced to the three types of test:

  • Unit tests

  • Integration tests

  • Functional (E2E) tests

You also saw that, while manual helper code tests can work, there are automated frameworks which make testing easier and more reliable.

In the next chapter, we will have a look at how to fix code when it goes wrong: debugging! 

Example of certificate of achievement
Example of certificate of achievement