Lesson Monday

Now that we’ve set up Jasmine, it’s time to test some code. Let’s revisit a project from Introduction to Programming: Triangle Tracker.

A quick refresher: a Triangle Tracker application should determine whether or not three lengths can be made into a triangle, and if so, what kind of triangle it would be. We’re going to use Red-Green-Refactor approach to solve this problem one behavior at a time.

Jasmine automatically created a spec directory in our root directory when it was installed. Let’s put our first spec file in there: triangle-spec.js. Now we can write a test that will check if we’ve properly instantiated a triangle object.

triangle-tracker/spec/triangle-spec.js
describe('Triangle', function() {

  it('should test whether a Triangle has three sides', function() {
    //Test content will go here.
  });
});

Before we write the rest of our test, let’s go over a few key concepts. We use describe() to define a suite. A suite allows us to group and organize tests. Note that we don’t need to describe this suite as a Triangle. We could describe it as 'the Triangle and all its prototypes'. The description is there to make our code more readable. Our suite should be described in a concise and descriptive way, and since it’s a suite that describes Triangle and its prototypes, describing it as a Triangle makes sense. If we were to test other shapes such as squares and circles, they’d have their own describe block.

Within our describe() block, we have a spec. Each spec begins with it(). Once again, we describe the content of the test, which generally begins with the word “should.”

If we run our test using $ npm test in the console, it passes. That’s because our test doesn’t have any expectations yet. Let’s add some code to make the test fail.

triangle-tracker/spec/triangle-spec.js
describe('Triangle', function() {

  it('should test whether a Triangle has three sides', function() {
    var triangle = new Triangle(3,4,5)
    expect(triangle.side1).toEqual(3)
    expect(triangle.side2).toEqual(4)
    expect(triangle.side3).not.toEqual(6)
  });

});
  • In our spec, we create a new Triangle object and write a set of expectations to check if the object has been instantiated.

  • toEqual() is a matcher. Matchers provide a boolean value for each expectation. If the value is true, the spec passes. If it’s false, it doesn’t.

  • The third expectation is a bit silly, but it illustrates an important feature of Jasmine matchers. We can chain not to any matcher to test its opposite. In this case, we expect the third side of triangle not to equal a certain value.

Let’s run our tests again. The test fails, as expected:

ReferenceError: Triangle is not defined

We haven’t created a Triangle constructor yet, so let’s do that. Create a folder called js in your root directory and add the file triangle.js inside it. Here’s our Triangle constructor:

js/triangle.js
function Triangle(side1, side2, side3) {
  this.side1 = side1;
  this.side2 = side2;
  this.side3 = side3;
}

This code should work, so let’s run our test again. Hmm. Same error. That’s because we’ve forgotten to export our Triangle constructor into our test file. This is a quick fix.

js/triangle.js
function Triangle(side1, side2, side3) {
  this.side1 = side1;
  this.side2 = side2;
  this.side3 = side3;
}

exports.triangleModule = Triangle;

Let’s import the triangleModule into our test file as well:

spec/triangle-spec.js
var Triangle = require('./../js/triangle.js').triangleModule;
…

Now if we run npm test, the spec passes.

Let’s write one more test. We’ll check whether three lengths can be made into a triangle or not.

spec/triangle-spec.js
...
describe('Triangle', function() {

  ...

  it('should correctly determine whether three lengths can be made into a triangle', function() {
    var notTriangle = new Triangle(3,9,22)
    expect(notTriangle.checkType()).toEqual("not a triangle");
  });

});

One thing to note here: we’re using plain old JavaScript in our test. We declare and assign the notTriangle variable and then we call the method checkType() on it. This may seem obvious, but it’s worth pointing out that our tests are really just plain old JavaScript with some Jasmine helpers thrown in.

Once again, the failure is expected: TypeError: notTriangle.checkType is not a function. We need to write the method to make it pass:

js/triangle.js
...
Triangle.prototype.checkType = function() {
  if ((this.side1 > (this.side2 + this.side3)) || (this.side2 > (this.side1 + this.side3)) || (this.side3 > (this.side1 + this.side2))) {
    return "not a triangle";
  }
};

We now have two passing tests, but the test suite isn’t complete yet. We should also have tests to check different triangle types as well. The next step is to practice writing your own tests. You can build out the tests for triangle tracker or use another project from Introduction to Programming. You’ll get a chance to do this in class. As you work, remember that backend code should be separate from the user interface. This will make it much easier to unit test your code.

Take a look at Jasmine’s documentation. In particular, you should take the time to explore Jasmine’s built-in matchers.

Before and After Blocks

One other thing you should know: sometimes you’ll need to reuse code for multiple specs. For example, you might want to instantiate the same object in different specs so you can test it in different ways. If this is the case, use beforeEach() to DRY up the code. Here’s an example:

spec/triangle-spec.js
...
describe('Triangle', function() {
  var reusableTriangle;

  beforeEach(function() {
    reusableTriangle = new Triangle(5, 5, 5);
  });

  it('should show how beforeEach() works', function() {
    console.log(reusableTriangle);
  });
  …

Here we declare reusableTriangle in the top-level scope. The beforeEach() function then assigns an instantiated object to the reusableTriangle variable. This object will be made available to each spec inside the suite. Now we don’t need to define our reusableTriangle in each spec, which makes our code DRYer.

If we run our tests, we’ll see that the value of the console.log() is printed to the terminal: Triangle { side1: 5, side2: 5, side3: 5 }. It may come as a bit of a surprise that console.log() prints to the terminal, but that is built-in functionality that Node provides. The benefits of console.log() aren’t limited to the browser!

There’s also an afterEach() function as well. This function is particularly useful if you need to perform some kind of teardown after each spec. For instance, if state from one spec somehow ends up in another spec, it could result in a failed spec or other unexpected behavior. Check the documentation for further information.

So far we’ve used Node to run our Jasmine tests. In the next lesson, we’ll use Karma as a test-runner. Together, Karma and Jasmine provide a robust testing framework that will allow us to continuously run our tests.