Now that we’ve set up Jasmine, it’s time to test some code. Let’s revisit the Triangle Tracker project we completed back in Intro. As you may recall, this Triangle Tracker determines whether three provided lengths can successfully create a triangle. If so, it also lists what kind of triangle it would be (equilateral, isosceles, or scalene). This time, we'll use the BDD Red-Green-Refactor workflow to solve this problem again.

Writing Specs

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.”

Now, we can try running $ npm test to make sure our configuration is setup properly. We get an error! It looks like ESLint doesn't recognize our Jasmine specific code. Remember how we updated the env section of .eslintrc so that ESLint wouldn't throw errors for JQuery syntax? We need to update our list for env to include jasmine:

.eslintrc
...
  "env": {
    "es6": true,
    "browser": true,
    "jquery": true,
    "node": true,
    "jasmine": true
  },
...

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 src in your root directory and add the file triangle.js inside it. Here’s our Triangle constructor:

src/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.

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

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

spec/triangle-spec.js
import { Triangle } from './../src/triangle.js';
...

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 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:

src/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.

You're now equipped to write and run your own tests! In the next lesson, we'll practice debuging with Karma and Jasmine.

Sample Repo

If you'd like to see an example of how tests should be written with Jasmine, check out this sample Triangle Tracker repo. While you are welcome to follow along with this example tomorrow, it should be used primarily as a reference point. Focus on writing your own tests and get as much practice as you can.