This workshop looks at writing unit tests in Jest.

Introduction

This workshop goes with Sprint 3 of Structuring and testing data (See https://programming.codeyourfuture.io/structuring-data/sprints/3/

What you should already know

As a trainee you should:

What you'll learn

What you'll need

You will need a computer with the latest versions of this software installed:

These are the versions I have installed:

$ node --version
v21.2.0
$ npm --version
10.9.0

What you'll do

The following pages will take you through all the steps of the workshop.

  1. Open the workshop instructions which can be found here: https://beaumoaj.github.io/jestWorkshop/#0
  2. You should fork and then clone the workshop repository which you will find here: https://github.com/beaumoaj/jestWorkshop. Note this repository is currently private, I will make it public at the start of the workshop for you to fork it.

Once you have forked the repository and cloned a copy to your computer, open the folder in VS Code. It should look like this:

Project Open In VS Code

This project contains the following files:

Running the code

  1. Open the package.json first and look at the dependencies (you will see jest) and look at the scripts - you will see demo and test. The scripts definitions allow you to run two actions, demo and test (don't run them yet).
  2. Open the file called dayDemo.js. It constains the following code:
    const {
      setUpDay,
      makeAppointment,
      getAppointment,
      getLengthOfDay,
      getFirstAppointmentTime,
      getLastAppointmentTime,
      printAppointments,
    } = require("./day");
    
    // The following is just a demo of how to use the functions.
    // Remember to call setUpDay() before making any appointments.
    // If you want to debug a test, you can change the code below to copy the test code.
    console.log("Day Demo.  Setting up an empty day.");
    setUpDay();
    printAppointments();
    console.log("Making an appointment for John at 11am.");
    makeAppointment(11, "John", 1);
    printAppointments();
    
    At the top of the file we import the required functionality from day.js These are the functions you are allowed to call. Then we:
    • call setUpDay to initialise an empty appointment array,
    • print the appointments from the empty day
    • make a one hour appointment for John at 11
    • print the list of appointments again. This time we should see the new appointment at 11.
  3. Open a terminal window in the folder and enter:
    npm run demo
    
    You should see the following output:
    $ npm run demo
    
    > jest@1.0.0 demo
    > node dayDemo.js
    
    Day Demo.  Setting up an empty day.
    Appointments for the day:
    Time: 9, Name: available, Length: 0
    Time: 10, Name: available, Length: 0
    Time: 11, Name: available, Length: 0
    Time: 12, Name: available, Length: 0
    Time: 13, Name: available, Length: 0
    Time: 14, Name: available, Length: 0
    Time: 15, Name: available, Length: 0
    Time: 16, Name: available, Length: 0
    Time: 17, Name: available, Length: 0
    Making an appointment for John at 11am.
    Appointments for the day:
    Time: 9, Name: available, Length: 0
    Time: 10, Name: available, Length: 0
    Time: 11, Name: John, Length: 1
    Time: 12, Name: available, Length: 0
    Time: 13, Name: available, Length: 0
    Time: 14, Name: available, Length: 0
    Time: 15, Name: available, Length: 0
    Time: 16, Name: available, Length: 0
    Time: 17, Name: available, Length: 0
    
    Check the output is what you expected given the code in dayDemo.js.

Imagine that you have been asked to implement the function called makeAppointment. The starting code for this function can be found near the bottom of day.js and it's current implementation is:

// Make an appointment at a specific time.
// THIS IS THE ONLY FUNCTION THAT YOU CAN CHANGE
function makeAppointment(time, person, length) {
  updateIndex(time, person, length);
  return true;
}
  1. Don't change any code yet but open day.js and find the makeAppointment function. Also find the function called updateIndex which is the function that changes the contents of the array containing all of the appointments in the day.

If you were to debug the code with a break point at the end of makeAppointment you will see the following:

Debugging the makeAppointment function

Appointments made for 9am are stored in index 0, appointments for 10am are stored in index 1, appointments for 11am are stored in index 2.

Note that after we run the dayDemo and set an appointment for John at 11, index 2 of the appointments array has been changed to:

{ name: "John", length: 1 }

You can also see the values of the parameters length, person and time in the debugger.

The code for makeAppointment is only the starting point for the function. Before we can change anything, we need to know the specification for that function.

Here is the specification (ie the rules) for the makeAppointment function. The specification tells us how the function should work.

  1. Take some time to look at these rules.
  2. Write down five different scenarios for making an appointment that should return true (ie they are allowed)
  3. Write down five different scenarios for making an appointment that should return false (ie they should not be allowed).

The philosophy behind Test Driven Development (aka TDD) is that we should write a test first to check the functionality that we are about to write. We run the test and see that it fails. Then we implement the functionality and re-run the test and hopefully see that it passes.

  1. Open the file called day.test.js and have a look at the existing tests. There are three of them. Read the tests and try to understand what they do (do not run the tests yet).
  2. Do you understand what each of the three tests are testing for? If not ask a question.

A good test should should have three parts:

Example: testing that setUpDay works correctly

Here is the code to test setUpDay:

// test that all the appointments on an empty day are available
test("empty day", () => {
  // nothing to set up
  // perform the action we are testing
  setUpDay();
  // check the results are as we expected
  const start = getFirstAppointmentTime();
  const length = getLengthOfDay();
  for (let time = start; time < length; time++) {
    expect(getAppointment(time).name).toBe("available");
  }
});

Here there is nothing to set up. The action is that we call the function setUpDay. Checking requires we look at the array to see that name property has the value available for each hour of the day.

Example: testing we can make an appointment at 9:00

Here is the test to make an appointment at the start of the day (at 9:00).

// test that we can make an appointment at the start of an empty day
test("Make Appointment at start of day", () => {
  // set up everything ready for the test
  setUpDay();
  const time = getFirstAppointmentTime();
  // perform the action
  const result = makeAppointment(time, "John", 1);
  // check the results are as expected
  expect(result).toBe(true);
  expect(getAppointment(time).name).toBe("John");
  expect(getAppointment(time).length).toBe(1);
});

Here there are two things to set up, first we need to call setUpDay to ensure the day's appointments are all available, and then we find the time of the first appointment of the day. Next we perform the action which is to make the appointment and get the returned result. Finally we check that the returned result was true, the name associated with the assigned appointment is John and the length associated with the assigned appointment is 1.

Running the tests

  1. Run the tests by typing the following into the terminal window:
    npm run test
    
  2. Which tests pass and which test fails?
  3. Identify the failed test, where is it failing and what is going wrong. (do not change any code at this time)
  4. Explain what is causing the test to fail.

Discussion

  1. At this point we will have a discussion to check everyone is following and understands the tests.

Fixing the failed test

  1. Edit the code in makeAppointment to ensure that the method returns false as described in the specification. Do not modify any existing function except for makeAppointment.
  2. Rerun the tests and ensure that all tests pass.

You should work in pairs to complete the following steps. If you finish this step and have time to spare, repeat the steps and write tests for other scenarios.

  1. Pick one of your five scenarios that should cause makeAppointment to return true and write a test to ensure that everything it needs to do has been done correctly.
  2. Run the test and see it fail
  3. Write the code to implement your scenario
  4. Rerun the tests and ensure it passes before you continue
  5. Pick one of your five scenarios that should cause makeAppointment to return false and write a test to ensure that nothing has changed in the appointment diary and that the returned value is false.
  6. Run the test and see it fail
  7. Write the code to implement your scenario
  8. Rerun the tests and ensure it passes before you continue