Sunday 17 May 2015

Set Up Tests with Jasmine

Since I've been trying to do my entire build process through Grunt I've had to rethink how testing works. Up until recently I've either been using Visual Studio plugins (Chutzpah or ReSharper) or Protractor as a macro in WebStorm. Both of these require a very specific configuration in order to run Jasmine tests. The same is true for setting up Jasmine testing in Grunt. However, what doesn't change is how to actually write the tests. Once you've got the configuration for your particular test harness setup this is how to structure tests.

Example Code

describe('The service', function () {

    var myService = undefined;

    beforeEach(function() {
        module('myApp');

        inject(function (_myService_) {
            myService = _myService_;
        });
    });

    afterEach(function(){
        myService = undefined;
    });

    it('should exist', function() {
        expect(myService).not.toBeUndefined();
    });

    describe('truthy value', function() {

        it('should be true', function () {
            //arrange
            var truthy = true,
                returnedValue;

            //act
            returnedValue = myService.returnTruthy();

            //assert
            expect(returnedValue).toBe(truthy);
        });
    });
});

describe() and it()

The first thing in your spec should be a describe function. describe functions hold a collection of tests together in a related way. Within each describe you'll have one or more it function. Each it tests a single piece of functionality. The way that the functions are written helps you to make sure that you are grouping correctly, and testing one piece of functionality per it.

The first parameter of both the describe and it functions is a string that is concatenated and used to print out a human readable description of whether each test has passed or failed. Every describe that you have should be the beginning or middle part of a sentence and your it should be the end part of a sentence.

When you come to do more complex testing you want to think about implementing nested describe functions. The concept of how to structure describe and it functions is quite tricky to grasp in the beginning. Once this is understood it opens up the power of nesting correctly so that there can be automated reporting of test results to stakeholders or the customer in a way that they can understand without needing any technical knowledge.

A test for making sure a form exists on the page when the page firsts load may write out (in human readable form) as The registration page should have a registration form present. In this statement The registration page would be your describe function, and should have a registration form present would be your it function. Then you may want to test that when the user interacts with the page somehow that something else happens. Continuing this example, you may want to check that the form validates fields properly, so your sentence would be The registration page when the user submits the form should validate an email has been entered correctly. So, within the first describe function you would add another describe function with when the user submits the form as it's first parameter, then within that you could have multiple it functions, one of which checks the email validation and is labelled should validate an email has been entered correctly. All of your describe functions must be the beginning and middles of sentences and all of your it function statements must start with should and be the end of the sentence. These rules are not constrained in any way, but if you stick to them then you'll find yourself heading in the right direction both with the semantics for print out, and for the level of complexity that you're testing.

The second parameter of each of these functions is a function. This is where the similarity between the two functions ends.

It is important to remember at this stage that JavaScript has functional scope. This means, that anything declared within {braces} can be accessed by anything else within those same {braces}.

describe()

The second parameter for the describe function is a function that contains all the code to run the tests covered by the describe's grouping statement from the first parameter.

The majority of the function calls within any describe will be the it functions that contain the assertions you're making about your code. However, there may be things that you need to setup or teardown before and after each test. Rather than clogging up your it functions with the same repeated code you can use the beforeEach and afterEach functions.

Also within describe functions you might declare variables that are to be used in a global manner throughout subsequent function calls, such as variables that are manipulated by a beforeEach or afterEach function and then used within it functions.

it()

The second parameter for the it function is a function that contains the code that sets up and evaluates the set of assertions that are made to decide whether or not the test has fulfilled the description in the first parameter of the it function. I like to use the arrange, act, assert design pattern for writing these as it helps to keep things clear and tidy.

beforeEach() and afterEach()

These functions are beautifully self-descriptive. Any code that is written within the beforeEach function will be run before each and every it function within the parent describe function. Likewise, the afterEach function is run after each and every it function is run.

I use the beforeEach function to establish the initial state of variables, include my AngularJs module(s), to inject any services that I may need for testing, etc. There's plenty that can be done inside of here. I use the afterEach function less often, but when I do I typically do delete things; I reset any variables to undefined or some specific null state. This just makes sure that nothing is leaking from one test to another so as to not cause any false passes or false fails.

There are also beforeAll and afterAll functions that run once before and after all the it functions have run. I'm wary of these functions. I'm sure that they can be used effectively if used carefully, but I really dislike things that need to be used carefully.

Arrange, Act, Assert

Within each it function I find it useful to break the space into three areas; arrange, act, and assert. This is a nice pattern that indicates the three stages of each test. If you have more than a few lines in any one of these sections it indicates that my test isn't as concise as it should be, or that I could be testing more than one thing.

Arrange

In the arrange section I prepare any variables that are needed to either pass to the piece of code that I'm testing (typically function parameters), or that I want to compare to the returned value. I also set up anything that I'm mocking, or spies that are checking that particular pieces of code have been called.

Act

This is where the code that is being tested is called. It really should only be one line of code.

Assert

This is where all the actual tests, the assertions, live. In Jasmine we have expect functions that allow other functions (known as matchers) to be chained after them to test the returned result is what was expected, or check that spies saw what they were meant to see. These return booleans, and for the it function to pass all expect chains need to return true.

No comments:

Post a Comment