04 Jul 2018

Code Companion #7: Automated Testing

Video

Introduction

In this tutorial we’re going to take a look at automated testing. Automated testing is a way for us to automate the testing of our code—something we’ve been doing manually up until this point.

Think about the functions we’ve written so far. We write a bit of code, we run it, and then we manually verify the code is giving us the result we expect. With automated testing, we can automate ourselves out of this process.

We’ll be taking a test-driven development (TDD) approach meaning we’ll write our tests first. These tests will initially be failing (referred to as red in TDD) because the actual code hasn’t been written yet. Then we’ll write the code, run the test again, and (hopefully) verify that our tests are passing (referred to as green in TDD).

Join the club! Click here to get exclusive content with lessons I learned going from self-taught to self-employed developer, updates on my latest tutorials, and my recommended articles to help you stay up-to-date on industry trends.

Setup

Before we get into the code, create a new directory for the code we’ll write in this tutorial.

mkdir webdev-testing

Then use cd to move into your new directory.

cd webdev-testing

Within this directory, create the new file for your code.

touch webdev-testing.js

Jasmine

To test our code we’ll need to leverage a testing framework. A testing framework provides us the tools we need to test our code without having to write all of it ourselves. The one we’ll be using for this tutorial is Jasmine.

Since our code has a dependency, Jasmine, we’ll start by creating a package.json file using the npm init command (npm was installed way back when we installed Node.js in Code Companion #2). Run the following command in the webdev-testing directory.

npm init

Run that and you should see a prompt asking you for input such as the project’s name, version, description, git repository, etc. Feel free to simply hit Enter to these to leave them with their default values.

npm init prompt

After that is finished you should now see a package.json file in your directory. This file contains details about our code such as its dependencies (like Jasmine) and metadata such as the project’s description and author. Assuming another developer looked at this project they wouldn’t need to ask which testing framwork was used. They could simply look at package.json and see for themselves.

So now let’s install Jasmine and add it to package.json. First we’ll install Jasmine globally to give us access to the Jasmine CLI.

npm install jasmine -g

Then we’ll add Jasmine to our project’s development dependencies using the -D flag.

npm install jasmine -D

A development dependency simply means that the dependency is necessary for development purposes. Testing code is something that’s done in development before code is released to the world (production). So the -D flag is a way to signify that this dependency isn’t needed for our code to actually run, only to aid us as we’re writing the code.

From here we can initialize the project for Jasmine by running jasmine init to create our testing directory.

jasmine init

After that has finished your directory should now look like this:

webdev-testing
+-- node_modules
+-- package.json
+-- spec
+-- webdev-testing.js

The package.json file was created earlier when we ran npm init. Then when we installed Jasmine as a project dependency, it created the node_modules folder which contains Jasmine and all of its dependencies. Finally, we have the new spec directory that was created with jasmine init. This is where we will write our tests.

Move into this directory and create a new file. Unlike the file for our code, webdev-testing.js, this file should have the extension .spec.js. This is the file extension Jasmine looks for when it runs tests.

cd spec
touch webdev-testing.spec.js

With that file in place you can now run jasmine in your terminal to run the tests.

jasmine

Jasmine no tests

The output from that command should say something like “No specs found”. That’s because we haven’t written any tests. So let’s start with our first automated test.

Our first test

We begin our automated tests by calling the describe function provided to us by Jasmine. This function is for naming a collection of tests and we provide it two arguments: the name of our collection of tests and a function.

Within the function we call the it function. This is what’s known in Jasmine as a “spec”. Similar to describe it takes two arguments: the name or title of the spec and a function.

Within the function passed into it is where we write our tests as shown below:

describe('A suite', function() {
  it('contains a spec with an expectation', function() {
    // Test code here
  });
});

At the moment there isn’t any code. Let’s add something simple to show Jasmine in action.

describe('A suite', function() {
  it('contains a spec with an expectation', function() {
    let myValue = true;
    expect(myValue).toBe(false);
  });
});

Here we create a variable myValue and assign it the value true. Then we call the expect function passing it what’s known as an “actual”. This is the bit of code we want to test.

Following that is what’s known as a “matcher”, toBe(). The value provided here is the “expected value”. So our test in plain English is, “Expect myValue to be false”.

The matcher toBe() is just one of many matchers provided to us by Jasmine which you can see here.

To run this test open a terminal and navigate to the webdev-testing directory. Then run jasmine.

First test, failing state

You should now see Jasmine highlighting errors in the test. On line 4 it expects true to be false. Our test is currently in a failing (red) state. So let’s get it to pass.

describe('A suite', function() {
  it('contains a spec with an expectation', function() {
    let myValue = false;
    expect(myValue).toBe(false);
  });
});

With the value of myValue updated to false we can now run Jasmine again with the jasmine command.

First test, passing state

Updating Date

Let’s continue by writing a test for a new and better Date, specifically a function to provide us with the current day of the week in English as we did in Code Companion #5.

First, we’ll start by writing a failing (red) test for our code.

describe('BetterDate', function() {
  it('should return the day of the week', function() {
    let betterDate = new BetterDate('02 Jul 2018');
    expect(betterDate.getDay()).toBe('Monday');
  });
});

As you can see, we begin by creating a new instance of BetterDate passing it a date in string form. Then we call the .getDay() function within expect() and chain it with the .toBe() matcher providing it the expected value Monday.

Run the jasmine command and you should see an error for our failing test because BetterDate is not defined.

First test, failing state

It’s worth noting how Jasmine prints errors as well. The first item under “Failures” reads “BetterDate should return the day of the week”. The strings we passed to describe and it combine to read like an English sentence. As these files grow in size, it’s helpful for identifying where the error occurs and giving us an easily digestible way of understanding why our code is failing.

Now we can implement the constructor function BetterDate to get this test to pass. Just above describe add the following code.

function BetterDate(date) {
  this.now = new Date(date);
  this.getDay = function() {
    const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
    let day = this.now.getDay();
    return days[day];
  };
}

describe('BetterDate', function() {
  // Code below omitted for brevity
  ...
});

In this constructor function is a now property that’s set to an instance of Date using the date parameter. Then we define the getDay function with an implementation that should look similar to one we wrote in Code Companion #5.

Run jasmine and you should now see this test in a passing (green) state.

First test, passing state

However, when we write code we typically don’t add our code to test files. We have test files to test code but it doesn’t need to be in the exact same file. Instead, we’ll put the code for BetterDate into its own file and import it within our test file webdev-testing.spec.js.

To do this, first move the BetterDate function into webdev-testing.js and add another line at the bottom to export it.

function BetterDate(date) {
  this.now = new Date(date);
  this.getDay = function() {
    const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
    let day = this.now.getDay();
    return days[day];
  };
}

module.exports = BetterDate;

The line module.exports = BetterDate; is the line that exports, or exposes, our code. It’s a special object provided to us by Node.js specifically for this purpose. Adding BetterDate to module.exports allows consumers of this code, such as our testing file, to use this constructor function.

Unfortunately moving this function has reverted our test back to a failing (red) state. Running jasmine should now fail because BetterDate is no longer defined. To fix this we’ll need to import the file we just created.

let BetterDate = require('../webdev-testing');

describe('BetterDate', function() {
  it('should return the day of the week', function() {
    let betterDate = new BetterDate();
    expect(betterDate.getDay()).toBe('Monday');
  });
});

Here we use the require() function (also provided to us by Node.js) to import the file we just created setting it to the variable BetterDate. We provide require() the path to the file webdev-testing.js (the .js in require is optional).

Run jasmine again and the test should now be back to a passing (green) state.

File paths

You may be wondering about the ../ that precedes webdev-testing in require().

Since our testing file webdev-testing.spec.js lives in webdev-testing/spec we need to move up one directory to access webdev-testing.js. For that reason we add ../ which gets us one directory higher than where the file lives.

webdev-testing
+-- node_modules
+-- package.json
+-- spec
  +-- webdev-testing.spec.js
+-- webdev-testing.js

You can see this in command-line form in your terminal. If you’re in webdev-testing run the following commands:

cd spec
cd ..
cd spec
cd ..

Once you’re in spec the .. allows you to move back into webdev-testing. The ../ in require('../webdev-testing'); is doing the same thing.

Conclusion

Before we finish, add your latest changes to Git.

git init
git add .

Then add a commit message.

git commit -m "Add automated testing"

Then push these changes up to GitHub.

git push origin master

Exercises

For this exercise, create another test file in the spec directory.

touch spec/trainer.spec.js

In that file add the following tests.

let Trainer = require('../trainer');

describe('Trainer', function() {
  it('should have a name', function() {
    let trainer = new Trainer('Adam');
    expect(trainer.identify()).toEqual('Trainer is Adam');
  });

  it('should capitalize the trainer\'s name', function() {
    let trainer = new Trainer('adam');
    expect(trainer.identify()).toEqual('Trainer is Adam');
  });

  it('should have tasks', function() {
    let trainer = new Trainer('adam', ['Get a Pokemon']);
    expect(trainer.printTasks()).toEqual('Remaining tasks: Get a Pokemon');
  });

  it('should default to an empty array if no tasks are provided', function() {
    let trainer = new Trainer('adam');
    expect(trainer.tasks).toEqual([]);
  });

  it('should print a message if no tasks remain', function() {
    let trainer = new Trainer('adam');
    expect(trainer.printTasks()).toEqual('No tasks left!');
  });

  it('should add new tasks to the tasks array', function() {
    let trainer = new Trainer('adam', ['Get a Pokemon']);
    trainer.addTask('Visit Professor Oak');
    expect(trainer.printTasks()).toEqual('Remaining tasks: Get a Pokemon, Visit Professor Oak');
  });

  it('should remove a task from a tasks', function() {
    let trainer = new Trainer('adam', ['Get a Pokemon', 'Visit Professor Oak']);
    trainer.removeTask('Get a Pokemon');
    expect(trainer.printTasks()).toEqual('Remaining tasks: Visit Professor Oak');
  });

  it('should verify the task exists before attempting to remove it', function() {
    let trainer = new Trainer('adam', ['Get a Pokemon', 'Visit Professor Oak']);
    trainer.removeTask('Beat Brock');
    expect(trainer.printTasks()).toEqual('Remaining tasks: Get a Pokemon, Visit Professor Oak');
  });
});

Then create another file in webdev-testing.

touch trainer.js

Within that file, add the rough scaffolding for Trainer that’s shown below.

function Trainer() {
  // Your code here
}

module.exports = Trainer;

Run jasmine and you’ll see 8 failing tests. Write the code for Trainer to make all the tests pass.

Need some help? Click here to join a Discord server created exclusively for this series.

If you only want jasmine to run the tests within trainer.spec.js you can update the describe function to fdescribe. This will “focus” Jasmine to this set of tests ignoring the tests in other files such as webdev-testing.spec.js.

let Trainer = require('../trainer');

fdescribe('Trainer', function() {
  ...
});

Just remember to change it back to describe once you’re done to see all of your tests in a passing state!


Before you go...

Feeling stuck copying and pasting from tutorials that don't build realistic applications?

Become an Angular developer by building a real application using open-source libraries with a real API and database.

"The advice and techniques in this book landed me my dream job and literally doubled my salary. Well worth the $99 bucks, so much so that I have been waiting for it to be for sale just to pay the man for the good he has done to my career." -Levi Robertson

CLICK HERE FOR A FREE 8 CHAPTER PREVIEW OF THE ANGULAR TUTORIAL

Learning a front-end framework is hard. "Getting started" tutorials cover the basics but you leave thinking, "Okay, now how do I build something with this?"

The truth is, getting started tutorials aren't all that great for beginners. They're demos to highlight as many features as quickly as possible.

They're great for showing off what a framework can do. They aren't so great for teaching you how to build web apps.

The end result is a basic application that doesn't mimick what it's like building real applications as a front-end developer.

You'll work with a mocked API and database. Application architecture isn't covered. Automated testing is skipped altogether.

Trust me, I've been there. But those days are over.

With The Angular Tutorial, you'll learn how to build applications using a real API and database. You'll leverage 3rd party APIs like Zomato, Google Places, and open-source libraries just as you would in a real job.

The Angular Tutorial assumes you have no previous knowledge of the Angular framework. It starts at the very beginning.

Every piece of code is explained and tested to make you interview ready.

Ready to get started? 👇