Jest : Your First Test

Jest is an awesome testing library maintained by employees at facebook and open source contributions. Jest is used for running tests on your code. It both runs your tests and provides the methods needed to write the tests.

Jest Documentation

Installation

To install jest, create a package.json file in your project directory, you can use npm init. Then install jest to that project with the command:

npm install --save-dev jest

We will install jest locally as a dev dependency to each project we will be using it in. Once installed, we need to teach our package.json that we are using Jest as our tester. We do this by making the following change to the test script which has default text "test": "echo \"Error: no test specified\" && exit 1":

{
  "name": "testing-with-jest",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "jest",
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "jest": "^23.6.0"
  }
}

With that you are now ready to use Jest!


Why Testing, and what is Unit Testing?

To a lot of new developers, testing seems like an extra step that involves a bunch of extra code. In reality, testing is essential to a developers daily life (and sanity). As smart as we would like to think we are, your will quickly get to the point in your programming skills where you write code large enough/complex enough that even you get lost looking through it. Wouldn't it be nice if you could just enter a command and test all of your functions at once?

Now imagine you are on a team with other developers and something in your code breaks. Wouldn't it be great if your co-worker could just run your tests to see what part of the application is failing? These are just two of the many benefits that come from writing tests and approaching your programming with a TDD mindset.

What is unit testing?
Unit testing is the practice of breaking your program into smaller pieces and inspecting each piece with tests.

Tests run when changes are made to the code in your projects. The more often you run your tests the better.This helps you catch problems sooner and write better code.


Basic tests

Now that we've defined testing, let's look at how it's implemented. Start by making a sum.js file that declares a function sum and exports it.

function sum(a, b){
    return a + b
}

module.exports = sum

Now we want to make a test file for this function. Fortunately, jest makes this very easy. We can make a separate file with the word .test before the .js, and jest will know to check that file for tests when we run it. So for our sum.js file, we'll name the test file: sum.test.js.

In this file, let's write our first test using the global Jest function test:

var sum = require('./sum')

test('adds 1 + 2 to equal 3', function() {
    expect(sum(1, 2)).toBe(3)
})

Let's talk about the different parts here.

In our sum.js file, we declared a function that outputs the sum of two numbers, and then using the node module.exports, we make it available to other files in our project.

Next we made a sum.test.js file for Jest to run. In this file we first need to require the function from our other file so we can use it. Make sure you use the relative path on the require.

If you type var sum = require('sum'), node will look in the node_modules folder for a package called sum, then throw an error when it couldn't find it. Instead, give the relative path to the file such as var sum = require('./sum') where the ./ denotes that the file is on the same level of the directory that we are trying to require it from.

Next we wrote our actual test using the Jest test function. The test function takes two arguments: test(arg1, arg2).

  • arg1: A string explaining what the test is testing.
  • arg2: An anonymous function that will trigger the test evaluations.

In the anonymous function, we are using two other Jest functions, expect() & toBe(). These are wonderful methods that read like english, making testing your code as simple as possible.

Base Syntax: expect().toBe()

  • expect: This is given a function call, value, etc.
  • toBe: This is given the value expected to be returned inside of the expect

Note: Notice we are not having to require anything to use test(), expect(), or toBe(). Jest will automatically run tests for files ending in .test.js, making it so we do not have to require those methods in each test file.

Let's run the test now and see how jest looks when we are using it. In the console, run the command npm test. After a moment, you should see a screen similar to this:

Screen-Shot-2018-10-25-at-12.17.08-PM

Notice the pleasing green color of passed tests, this is what you will strive for as you approach programming with a TDD mindset (Test Driven Development).

Let's see what a failed test looks like. Break the sum function in the sum.js file now by making it return a * b instead of a + b. Now run the test again with npm test:

Screen-Shot-2018-10-25-at-12.33.42-PM

Go ahead and switch that function back to a + b once you have seen the failed test screen.

Here we have seen that expect().toBe() is great for comparing primitive values. Meaning it's great to compare a string to a string, number to number, and boolean to boolean. Ex:

  • expect(5).toBe(5)
  • expect('hello').toBe('hello')
  • expect(true).toBe(true)

What if the data type being returned was an array? Let's build another function and test to look at this.

Create a file called reverse.js and write this function that takes and array and returns a reversed version of the array:

function reverse(array){
    return array.reverse()
}

module.exports = reverse

Then create a reverse.test.js and write the following test:

var reverse = require('./reverse')

test("returns the given array reversed", function(){
    expect(reverse([1, 2, 3])).toBe([3, 2, 1])
})

Now run npm test and let's look at the output:

Screen-Shot-2018-10-25-at-2.17.54-PM

You should see the reverse test Fail and the sum test Pass. The 'Expected' and 'Recieved' appear to be presenting the exact same data. Why is this failing?

If you read through the failed test information, Jest gives you a hint on what you should be doing. For deep equality only, use toEqual instead.

Deep Equality

Deep equality refers to comparing complex data types against each other, such as object to object or array to array. Deep equals is a process of checking the inner contents of an array or object to make sure the two being compared match.

We can compare "someString" === "someString" because they would both take up the same spot in memory. Asking if array1 === array2 will always be false since they are two different pieces of data. This is true even if the contents of array1 and array2 were identical. This is why deep equality checking is needed to check the contents.

In Jest, deep equality can be checked by using the expect().toEqual() method instead of the expect().toBe(). In your reverse.test.js, change the .toBe([3, 2, 1]) to now be .toEqual([3, 2, 1]), and then run your test.

You should see both of your tests have passed!

Methods like expect(), toBe(), and toEqual() are called matchers in Jest. Below are examples of other matchers from the Jest Documentation(matchers):

// Expecting something to not be something else
  expect(0).not.toBe(1) 
  
// Checking Types
  expect(null).toBeNull();
  expect(n).toBeDefined();
  expect(n).not.toBeTruthy();
  expect(n).toBeFalsy();
    /* others in jest docs */
    
// Checking numerical values
  expect(4).toBeGreaterThan(3);
  expect(3.6).toBeGreaterThanOrEqual(3.5)
  
// Checking array contents
  expect([1, 2, 3]).toContain(2)

Congrats, you have learned the very basics of Jest, and with these basics you should now begin to write tests for your programs! Jest has a lot more to it than what we have looked at though, so let's take a look at a few more methods we can use.


describe()

So far we have written singular tests for our functions, but typically you will write multiple tests per function. Jest provides a wrapper that allows you to group tests that concern a particular function/feature called describe.

Take our sum.js function for example. What if we were to call that function with the strings "a" and "b" instead of two numbers? Let's write a test to make sure that doesn't happen. Go back to the sum.test.js file and wrap the existing test within a describe like the following:

var sum = require('./sum')

describe("The sum function", function(){

    test('adds 1 + 2 to equal 3', function() {
        expect(sum(1, 2)).toBe(3)
    })

})

Notice the syntax for describe is the same as the syntax for test. describe takes a string as a first argument, and a function as the second argument. The string should describe the thing you are testing. In this case, that is the sum function.

Now add a new it test inside of the describe block so you end up with the following.

    it("should receive two numbers as arguments", function(){
        expect(sum("a", true)).toBe("You must provide 2 numbers!")
    })

Screen-Shot-2018-10-28-at-10.38.19-AM

it and test are interchangeable in Jest, we are using it in this scenario for the syntatic difference of being able to say it should do something rather than test the following. Asking yourself it should ... questions about your function can help you get started on what tests to write.

If you npm test now you will see that our sum function isn't perfect after all. We are testing the function to see how it will react with non-numerical values and expecting it to return a message stating "You must provide 2 numbers!".

Now that we see the test failing, we have set a goal for our function on how to improve it. Try to fix the sum.js function now so that this test will pass. When you are ready, scroll below and we'll continue.


You may have come up with a solution like this:

function sum(a, b){
    if(typeof a !== "number" || typeof b !== "number"){
        return "You must provide 2 numbers!"
    }
    return a + b
}

module.exports = sum

Great, the test will pass with this added in. Our function is still not foolproof, but you can get the idea here of how to think of tests and strengthen your function logic.

One more test we could write for this function is "what should it return?". It should return a number, which would make our entire describe block look like the following:

var sum = require('./sum')

describe("The sum function", function(){

    it("should receive two numbers as arguments", function(){
        expect(sum("a", true)).toBe("You must provide 2 numbers!")
    })

    test('adds 1 + 2 to equal 3', function() {
        expect(sum(1, 2)).toBe(3)
    })

    it("should return a number", function() {
        expect(typeof sum(1, 2)).toBe("number")
        expect(typeof sum(5, 40)).toBe("number")
    })

})

As shown in this new it block, you can test as many cases as you deem fit for each condition, so here we are checking two different sum() executions to check the output.


Testing code is something not many bootcamp graduates are able to say they have practiced, but when applying for jobs it is often near the top of the list of requirements. Make testing a priority by approaching code challenges and functions with these methods. Start with simple tests that test simple functions, use the Jest documentation when you get stuck, and then rest assured that regardless of the size of your program, all it will take is an npm test to see if anything is broken!