Unit Testing in JavaScript

Unit Testing in JavaScript

The different automated testing types

  • Unit tests
  • Integration tests
  • End-to-end tests

What is a Unit Test

Unit tests are also known as component tests. They take the smallest piece of testable code (in most cases functions) and check if it behaves as expected.

Writing effective tests

It is possible to write good and bad tests. In the worst scenario, a bad test gives a false positive result for code that does not even work, giving developers a false sense of security.

As example, we are going to write a function that adds up two numbers.

const add = (a, b) => a + b;
const wrongAdd =(a, b) => a*b;

If we write a test for this function that checks if the result of add(2,2) === 4 is true, it is a bad test then wrongAdd(2,2) === 4 is also true.

// Bad test
expect(add(2,2).toEqual(4)); // true
expect(wrongAdd(2,2).toEqual(4)); // true (false positive)

To avoid writing bad unit tests you should:

  • Use a representative range: Use different parameters when writing tests. In our previous example, if we had written an additional test with different parameters, the false positive result would not have occurred.
  • Cover edge cases: in other words special cases that your function does or does not accept. E.g. an addition function where certain strings are allowed as parameter add("6", 5).

Unit Testing Libraries

Writing a Unit Test

We are going to create a function that returns the length of the shortest word of a sentence and write a Unit Test, with jest, that checks if the function works as indented.

1. Set up a new project

First, we need to create a new project in which we can work.

# Init project
$ yarn init -y
# Install modules
$ yarn
# Add Jest
$ yarn add -D jest
# Create src directory
$ mkdir src
# Create src file
$ touch src/shortest-word.js && touch src/shortest-word.test.js
# Install babel to use es6 synstax
$ yarn add -D @babel/core @babel/preset-env @babel/register
# Create a babel configuration file
$ touch .babelrc
.
├── node_modules/
├── src/
│   ├── shortest-word.js
│   └── shortest-word.test.js
├── package.json
├── .babelrc
└── yarn.lock

Edit .babelrc.

{
  "presets": ["@babel/preset-env"]
}

2. Write a function

Edit src/shortest-word.js and add the following code:

export const shortestWord = (s) => {
  // Split the sentence into an array of words.
  const words = s.split(" ");

  // Only keep shortest word.
  let shortestWord = words.reduce((a, r) => (r.length < a.length ? r : a));

  // Return length.
  return shortestWord.length;
};

3. Write a test

First we are going to test if the function returns the expected result when the sentence has a single shortest word.

describe("Should return the length of the shortest words", () => {
   describe("if there is only one shortest word", () => {
    test.each`
      result | sentence
      ${1}   | ${"He was sitting in a trash can with high street class."}
      ${3}   | ${"Greetings from the real universe."}
      ${1}   | ${"There's a message for you if you look up."}
    `(
      "the length of the shortest word should be $result in '$sentence'",
      ({ sentence, result }) => {
        expect(shortestWord(sentence)).toBe(result);
      }
    );
  });
});

Then we are going to test if the function returns the expected result when the sentence has several shorter words.

describe("Should return the length of the shortest words", () => {
  // ... previous test

  describe("if there are several shorter words", () => {
    test.each`
      result | sentence
      ${2}   | ${"The sunblock was handed to the girl before practice, but the burned skin was proof she did not apply it."}
      ${2}   | ${"He always wore his sunglasses at night."}
      ${3}   | ${"Potato wedges probably are not best for relationships."}
    `(
      "the length of the shortest word should be $result in '$sentence'",
      ({ result, sentence }) => {
        expect(shortestWord(sentence)).toBe(result);
      }
    );
  });
});

All the tests pass but not all the cases were covered, giving the developer a false sense of security. In other words, this is a BAD TEST. Then our function returns a false result when a sentence has a punctuation mark directly after the shortest word.

const sentence = "I, still don't know.";
shortestWord(sentence); // Returns 2 instead of 1.

So to fix this we are going to add another test.

describe("Should return the length of the shortest words", () => {
  // ... previous tests

  describe("if there is punctuation mark directly after the shortest word", () => {
    test.each`
        result | sentence
        ${1}   | ${"I, don't know anymore."}
        ${3}   | ${"Wow! Beautiful."}
        ${1}   | ${"There's something I! want to tell you"}
        ${2}   | ${"(is) chocolate tasty"}
        ${2}   | ${"he/she came from home"}
        ${3}   | ${"Goodbye mister you?"}
        ${2}   | ${"Where {is my} dog?"}
        // ... 
    `(
      "the length of the shortest word should be $result in '$sentence'",
      ({ sentence, result }) => {
        expect(shortestWord(sentence)).toBe(result);
      }
    );
  });
});

The new tests fail:

$ npx jest
 FAIL  src/shortest-word.test.js
  Should return the length of the shortest words
    if there are several shorter words
      √ the length of the shortest word should be 2 in 'The sunblock was handed to the girl before practice, but the burned skin was proof she did not apply it.' (2 ms)
      √ the length of the shortest word should be 2 in 'He always wore his sunglasses at night.'
      √ the length of the shortest word should be 3 in 'Potato wedges probably are not best for relationships.'
    if there is only one shortest word
      √ the length of the shortest word should be 1 in 'He was sitting in a trash can with high street class.' (1 ms)
      √ the length of the shortest word should be 3 in 'Greetings from the real universe.'
      √ the length of the shortest word should be 1 in 'There's a message for you if you look up.'
    if there is punctuation mark directly after the shortest word
      × the length of the shortest word should be 1 in 'I, don't know anymore.' (3 ms)
      × the length of the shortest word should be 3 in 'Wow! Beautiful.'
      × the length of the shortest word should be 1 in 'There's something I! want to tell you' (1 ms)
      × the length of the shortest word should be 2 in '(is) chocolate tasty'
      × the length of the shortest word should be 2 in 'he/she came from home' (1 ms)
      × the length of the shortest word should be 3 in 'Goodbye mister you?'
      × the length of the shortest word should be 2 in 'Where {is my} dog?' (1 ms)

4. Fix the function

Finally, we will have to fix our function to pass all tests.

To do, so we will split our sentences by blank spaces, punctuation mark or both, by changing the first line of our function.

// Split the sentence into an array of words.
  // Split by blank spaces and punctuation marks.
  const words = s.split(/\s*[.,#!$/?%^&*;:{}=\-_`~()]\s*|\s+/g).filter(s => s !== "");

Congratulation, all the tests pass!

Credits