Testing tool time with Jasmine

Enough theory! Let's get our hands dirty with some practical examples using the Alcohology app from Chapter 9, A Shopping Application. First, we'll drill down into the guts of the application and build a unit test for some of the key business functionality. Then, we'll jump up to a high-level view and check whether our building blocks integrate correctly.

There are a wealth of tools we can use for both. The Ext JS framework has begun to build a set of unit tests to verify its behavior and cut out regressions in core functionality. To do so, Sencha has chosen the Jasmine library.

Note

Sencha has hinted that there'll be a big announcement surrounding testing alongside SenchaCon 2015. Given that they're already using Jasmine, we can hope that it'll be a good bet for Ext JS application testing in the future.

Jasmine is a behavior-driven framework, a term that relates to the way tests are described. Rather than using the "assertation" terminology, it uses the "expectation" format that we briefly mentioned earlier in the chapter. It asks us to specify behavior and expect a particular result. Here's the canonical example from Jasmine's documentation:

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

The describe method encloses one or more specifications, themselves declared in an it method, with expectations declared using the expect method. To translate the previous code to plain language, use the following command:

We have "a suite", which "contains spec with an expectation". This expectation expects "true" to be "true".

Obviously, this is a contrived suite, as we'd hope that true would always be true! However, it should serve as a useful demonstration of the general syntax of a Jasmine test. Before we can get going and use this on our own application, we need to take a little bit of time to download and set up the Jasmine library.

Jasmine – installation and configuration

The simplest way of getting started with Jasmine is to download the latest version from the project's release page. At the time of writing this book, the current version is 2.1.3. Refer to https://github.com/jasmine/jasmine/releases for more information.

Extract the ZIP file and you'll see that the download includes some example specifications that we don't need; let's clear these out from within the new Jasmine directory:

rm MIT.LICENSE spec/* src/*

Now, we can move the Jasmine library to the root of the Alcohology project, assuming our current directory is now in the Alcohology project:

mkdir ./testsmv ~/Downloads/jasmine-2.1.3 ./tests/jasmine

We can now fire up our application; if you've downloaded the project files, then the readme file will tell you to run npm start and it'll start the Ext JS project and the API server. Once this is done, we can open http://localhost:1841/tests/jasmine/SpecRunner.html in our browser to run the specs, as shown here:

Jasmine – installation and configuration

The Jasmine spec runner before writing any specifications

In this screenshot, we can see the spec runner, but it's got nothing to do. We've got a little bit more configuration to do before we can start writing some specifications. Let's open up the SpecRunner.html file in an editor and tweak it to look like this:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Jasmine Spec Runner v2.1.3</title>

  <link rel="stylesheet" href="lib/jasmine-2.1.3/jasmine.css">

  <script src="lib/jasmine-2.1.3/jasmine.js"></script>
  <script src="lib/jasmine-2.1.3/jasmine-html.js"></script>
  <script src="lib/jasmine-2.1.3/boot.js"></script>
  <script src="../../ext/build/ext-all-debug.js"></script>

  <script type="text/javascript">
    Ext.Loader.setConfig({
      enabled: true,
      paths: {
        'Alcohology': '../../app'
      }
    });
  </script>

  <script src="spec/Cart.js"></script>
</head>
<body></body>
</html>

This HTML file is really just a host for the Jasmine library, but it's also where we wire up Ext JS to work outside of the context of an application. By including the ext-all JavaScript file and reconfiguring Ext.Loader to grab any Alcohology classes from the correct directory, we can instantiate classes to test and Ext JS will automatically request the files we need from our application directory. All that's left to do is include the actual JavaScript specification files at the bottom of the head element. Here, we've already added a reference to spec/Cart.js.

With all of the setup out of the way, we can move on to writing some tests!

Make it happen

Earlier, we wrote some pseudocode to illustrate how to test the addProduct method on the cart store. Now, let's build out the real Jasmine specification that accomplishes this for real. We need to create a test suite with a cart store that'll be used as test subject:

describe('Cart store', function() {

  var cart;

  beforeEach(function() {
    cart = Ext.create('Alcohology.store.Cart'),
  });
});

Our first suite is simply called Cart store. We have a cart variable that gets reassigned beforeEach specification is run. It's assigned an instance of the cart store via Ext.create. Thanks to our configuration in the previous section, Ext.create will use Ext.Loader to automatically pull in the relevant source code file, including any dependencies. By reinstantiating before every test, we can be sure that a test later in the suite won't be affected by the way an earlier test has manipulated the cart.

We can now sketch out the functionality we'd like to test. The following code goes after the beforeEach call:

describe('#addProduct', function() {
    it('should accept a Product model'),
    it('should create a new CartItem line'),
    it('should increment the quantity when adding an existing Product'),
    it('should not create a new CartItem when adding an existing Product'),
  });

If we refresh the SpecRunner.html page, then we'll actually be able to see something like this:

Make it happen

These specifications are just placeholders, but the fact that they show up in the runner is useful for developers practicing test first development. We can write a series of specification statements that describe the functionality we require, then the specs, and finally, the code itself. In this way, we're specifying the behavior we need and the code itself follows, and we can be safe in the knowledge that it meets our requirements. This can be a powerful methodology for an architect to spell out in detail how a class should behave.

Let's go through each spec one by one:

it('should accept a Product model', function() {
    expect(cart.addProduct.bind(cart, {})).toThrow();
});

We expect that if addProduct is passed, something that is not a product model, it will throw an exception. We pass the method to the expect call prepopulated with an empty object literal. As this isn't a product model—as expected—it throws an exception and satisfies the test as follows:

it('should create a new CartItem line', function() {
    var product = Ext.create('Alcohology.model.Product'),

    cart.addProduct(product);

    expect(cart.count()).toBe(1);
});

When the product is added to the cart, we expect that it will cause a new line item to be created in the store. We simply check whether the cart count is as expected after adding a product:

it('should increment the quantity when adding an existing Product', function() {
    var product = Ext.create('Alcohology.model.Product'),

    cart.addProduct(product);
    cart.addProduct(product);

    expect(cart.first().get('quantity')).toBe(2);
});

After adding a product that's already in the cart, we expect that it will increase the quantity of the corresponding cart line. We pass in the same product twice and check whether the quantity is two, as expected:

it('should not create a new CartItem when adding an existing Product', function() {
    var product = Ext.create('Alcohology.model.Product'),

    cart.addProduct(product);
    cart.addProduct(product);

    expect(cart.count()).toBe(1);
});

This is a similar setup as the last test, but we are expecting that there will not be a duplicated cart line, but instead, there will be only one item in the cart.

With all of the specifications written, we can refresh the spec runner again:

Make it happen

As you can see, the specs are all in green, indicating that they have passed successfully.

This is just a brief primer on unit testing with Jasmine, but it demonstrates the power available and the utility of testing in this manner. It gives us confidence in the code we've written and ensures that any additions to the addProduct method won't break the functionality that already exists.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset