|

Testing JavaScript With Jest

Testing JavaScript With Jest

As you build out an application, it is very easy to manually test things. You have been doing this since you started writing your first lines of code! How? Think about it. You write some code, save it, then run the code to see what the result is. Did it give you the result you wanted? Great! Then it worked. So if it is so easy to test the code you write, why write automated tests? The reason is that as your application gets bigger, it takes more and more time to manually test each part of the application.

manual application testing takes time


What are the Benefits of Testing?

Automated testing allows you to test your code frequently in a small amount of time. This means you can find problems and bugs before you actually push your code to production. If you’ve ever deployed an application for the first time, you know the worry that comes with that. Will it work? Are there bugs? Automated testing helps to give you more confidence that things will work when they need to. To be fair, testing is not going to save you from dealing with problems and bugs once software goes into production. It is impossible to write perfect software, no matter how many tests you write. However, instead of deploying an application with 25 bugs, maybe it deploys with only 5 thanks to all of your tests. Another benefit is allowing you to refactor your code with more confidence. In other words, you can clean up your functions, or change the structure of the code itself to make it more readable while still having the code perform the same exact task.


What are the Types of Tests in Software Development?

types of software tests
In general, there are three kinds of automated tests in software development.

  • Unit Tests: Test a small unit of an application without external resources like a database.
  • Integration Tests: Test the application with all external resources in place.
  • Functional / End To End Tests: Test the application through its User Interface.

The Pyramid of Tests

What types of tests should one right when building an application? If we have several types, which is best? Well, most likely you will have a combination of all three types of tests. The most amount of tests would be unit tests since they are easy to write an execute quickly. Next, you would have a collection of integration tests. There would likely be fewer integration tests in the application than unit tests. Lastly, you would have a few end to end tests, as these are the most comprehensive but also the slowest. This approach looks like a pyramid. Note the E2E stands for “End to End” or Functional.
software test pyramid
This means you would write most of your tests as unit tests. For any gaps that appear in test coverage, use integration tests. Lastly, use end to end tests the least.


Writing Your First Unit Test

So when do we get to write a test?! We are going to start that right now! First up, we are going to create a javascript-testing directory, then we can use NPM to install Jest. Let’s first use npm init to create a new package.json file.

javascript-testing $npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See npm help json for definitive documentation on these fields
and exactly what they do.
Use npm install  afterwards to install a package and
save it as a dependency in the package.json file.Press ^C at any time to quit.
package name: (javascript-testing)
version: (1.0.0)
description: JavaScript Test Tutorial
entry point: (index.js)
test command: jest
git repository:
keywords:
author:
license: (ISC)
About to write to C:\node\javascript-testing\package.json:

{
  "name": "javascript-testing",
  "version": "1.0.0",
  "description": "JavaScript Test Tutorial",
  "main": "index.js",
  "scripts": {
    "test": "jest"
  },
  "author": "",
  "license": "ISC"
}


Is this OK? (yes)

Now we can install Jest.
npm install jest

Now we should take a look at the highlighted line here in the package.json file. This is saying that when we run npm test at the command line, the jest script will run to execute all tests.

Let’s try it and see what happens:

javascript-testing $npm test
> javascript-testing@1.0.0 test C:\node\javascript-testing
> jest
No tests foundIn C:\node\javascript-testing
  2 files checked.  testMatch: **/__tests__/**/*.js?(x),**/?(*.)+(spec|test).js?(x) - 0 matches  testPathIgnorePatterns: \\node_modules\\ - 2 matches
Pattern:  - 0 matches
npm ERR! Test failed.  See above for more details.
javascript-testing $

Great! Jest is working but we don’t have any tests yet. Let’s create one! In our project, we can add a tests folder. In this folder we will place a utility.test.js file.
jest test example
The fact that the word test appears in the file name will let Jest know that this is a test. Here is our first test. It doesn’t test anything yet, but this will allow us to run npm test at the command line and see what happens.

Now we run our test, and check it out!
jest passing test

Above we see what a passing test in Jest looks like. Now, let’s see what a failing test looks like. We can modify our test like so.

Now when we run it, we get a whole lot of useful information.
jest test failed


How To Test Numbers

We have a function in our utility.js file which creates an absolute number. In other words, it will never return a negative number.

Let’s write a new test to test this. Here is the first iteration.

We can run the test, and it passes.

javascript-testing $npm test

> javascript-testing@1.0.0 test C:\node\javascript-testing
> jest

 PASS  tests/utility.test.js
  √ absolute - should return positive number for any positive input (6ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        6.351s, estimated 7s
Ran all test suites.

This function is also supposed to return a positive number even when given a negative number. No problem, we can add that to the test.

Now when we run the tests, it passes again. Great! Also note how we are shown how many tests ran.
two jest tests passing

Now let’s make a test fail to show how to use Expected vs Received values to troubleshoot. We know this function should return 0 if it is given 0 as input. We can make a test to fail for this condition like so.

Look at the output we get from a failing test.
troubleshooting failed tests in jest

That was just to show what the failed test looks like. We can fix that test, and now here is what the success of all 3 tests for this 1 function looks like.
three good jest tests


How To Test Strings

Now we can test a function that deals with strings. Consider this simple function that says hello to someone when then provide their name.

Let’s make a test for that. This test will use a new syntax.

The test looks good when we run it.

javascript-testing $npm test

> javascript-testing@1.0.0 test C:\node\javascript-testing
> jest

 PASS  tests/utility.test.js
  hello
    √ should return the hello message (7ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        6.977s
Ran all test suites.

Another option you can use when testing strings is the toContain() matcher instead of toBe(). The reason is that the test might be too specific and may fail easily. Below is a slightly more flexible version of the same test.


How To Test Arrays

Let’s take a look at some simple array tests we can use in Jest. Imagine we have this function which returns an array of stock tickers.

Here is the test we create for this. Check out the docs to understand how that arrayContaining() function works.


How To Test Objects

In this function we pass the id of a game, and the function returns a game object which has that id.

Here is a test that will ensure we get the right game.

When we run the test, all looks good!
jest toequal for testing objects


How To Test Exceptions

For functions that have the ability to throw an error, we need to take a different approach to the test. Consider this function which creates a new user. If the username is not provided, it will throw an exception.

Here is how we can test a function like this.

The test is passing when we run it.

javascript-testing $npm test

> javascript-testing@1.0.0 test C:\node\javascript-testing
> jest

 PASS  tests/utility.test.js
  createUser
    √ should throw an error if username is falsy (9ms)
    √ should return a user object when a valid username is provided (4ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        6.693s
Ran all test suites.

Testing JavaScript With Jest Summary

In summary, we have learned some of the following concepts regarding testing.

  • Writing code to test application code is referred to as automated testing.
  • Testing helps deliver software with less bugs and higher quality.
  • Testing helps you refactor with confidence.
  • Jest is a robust testing framework that has everything you need to test JavaScript.
  • There are three types of automated tests:
    • Unit Tests: Test a small unit of an application without external resources like a database.
    • Integration Tests: Test the application with all external resources in place.
    • Functional / End To End Tests: Test the application through its User Interface.
  • If tests are too general, they won’t ensure your code works. If they’re too specific, they tend to break too easily. Therefore aim for tests that are neither overly general, or overly specific.
  • You can use Mock functions to isolate application code from its external resources.
  • Jest matcher functions you might use in your testing:
    • // Equality
      expect(…).toBe();
      expect(…).toEqual();
    • // Truthiness
      expect(…).toBeDefined();
      expect(…).toBeNull();
      expect(…).toBeTruthy();
      expect(…).toBeFalsy();
    • // Numbers
      expect(…).toBeGreaterThan();
      expect(…).toBeGreaterThanOrEqual();
      expect(…).toBeLessThan();
      expect(…).toBeLessThanOrEqual();
    • // Strings
      expect(…).toMatch(/regularExp/);
    • // Arrays
      expect(…).toContain();
    • // Objects
      expect(…).toBe(); // for object references
      expect(…).toEqual(); // for equality of properties
      expect(…).toMatchObject();
    • // Exceptions
      expect(() => { someCode }).toThrow();
|