Wednesday, August 14, 2013

Testable Filters with TypeScript, AngularJS and Jasmine

The T6502 Emulator displays a set of registers to indicate the status of the program counter (where in memory the CPU is looking for the next set of instructions), the values of registers (temporary storage in the CPU) and a special register called the “processor status.” The processor status packs a lot of information into a single byte because it contains flag that indicate whether the last operation dealt with a zero value, a negative value (based on “two’s complement” addition whereby the high-order bit is set), if there as a carry from an addition, and so forth. It makes sense to display this register as a series of individual bits to see what’s going on.

The initial status is rendered like this:

PC SP A X Y NV-BDIZC Runtime IPS
0x200 0x100 0x0 0x0 0x0 00000000 0.000 0

To see how this is done through AngularJS, take a look at the source code:

<table>
    <tr><th>PC</th><th>SP</th><th>A</th><th>X</th><th>Y</th><td>NV-BDIZC</td><th>Runtime</th><th>IPS</th></tr>
    <tr>
        <td>{{cpu.rPC | hexadecimal}}</td>
        <td>{{cpu.rSP | hexadecimal}}</td>
        <td>{{cpu.rA | hexadecimal}}</td>
        <td>{{cpu.rX | hexadecimal}}</td>
        <td>{{cpu.rY | hexadecimal}}</td> 
        <td>{{cpu.rP | eightbits}}</td> 
        <td>{{cpu.elapsedMilliseconds / 1000 | number:3}}</td>               
        <td>{{cpu.instructionsPerSecond}}</td>               
    </tr>
</
table>

Notice the special braces for binding. They reference an object (the CPU) and a value. The pipe then passes the contents to a filter, in this case one called “eightbits” because it unrolls the status into its individual bits.The binding happens through scope. Scope is the glue between the model and the UI. Conceptually, scope looks like this:

angularscope

The scope was set to an instance of the CPU in the main controller:

$scope.cpu = cpuService.getCpu();

A powerful feature of angular is the ability to separate the declarative UI logic from the imperative business and presentation logic. In the case of the register, we want to show the individual bits. However, in the CPU model it is truly a byte register. The model of our CPU shouldn’t have to change just because someone wants to see the register in a different way – that is a design decision (it is part of the user interface and how the user experiences the information). Exposing the individual bits on the model is the wrong approach. Instead, we want to create a filter which is designed specifically for this scenario: manipulating output.

For convenience, I created a Main module that exposes a universal “App” class with some static fields. These fields make it easy to access and register modules. Keep in mind this is not necessary – you can access a module from anywhere within an angular app simply by calling angular.module() and passing the name. However, I find for both tests and the production app having something like this eases development and makes the references fast and easy.

module Main {
    export class App {
        public static Filters: ng.IModule = angular.module("app.filters", []);
        public static Directives: ng.IModule = angular.module("app.directives", []);        
        public static Services: ng.IModule = angular.module("app.services", []);
        public static Controllers: ng.IModule = angular.module("app.controllers", ["app.services"]);    
        public static Module: ng.IModule = angular.module("app"
            ["app.filters", "app.directives", "app.services", "app.controllers"]);    
    }
}

Note the “main” module ties in all of the dependent modules, but the filters are in their own module that can run independently of everything else. I’m using definition files from Definitely Typed to make it easy to discover and type the functions I’m using within angular from TypeScript. Now the filter can be defined. First, I want to define a spec for the filter. This describes how the filter will behave. To do this, I integrated Jasmine with the solution. Jasmine is self-described as “a behavior-driven development framework for testing JavaScript code.” It is also easy to use TypeScript to generate Jasmine tests.

The test harness simply includes angular, Jasmine, the main “app” that provides the statically registered modules, and then whatever individual pieces of the app I wish to test. In the case of the filter, I decided to call it “eightbits” so the eightbits spec looks like this (all of the source is available via the T6502 CodePlex site):

Note that I really was concerned with three cases – this is by no means an exhaustive test of every possible permutation. I want to be sure invalid input is simply echoed back, that valid input is displayed as bits and that if I have a small number that the bits are appropriately padded with zeroes so I get 00000001 instead of 1.

module Tests {

    describe("eightbits filter", () => {
  
        var filter: any;

        beforeEach(() => {    
            module('app');          
        });

        beforeEach(() => {
            inject(($filter) => {
                filter = $filter('eightbits');
            });
        });

        describe("given invalid input when called", () => {
            it("then should return the value back", () => {        
                expect(filter('zoo')).toEqual('zoo');                
            });
        });

        describe("given valid input when called", () => {
          
            it("then should return the bits for the number", () => {
                expect(filter(0xff)).toEqual('11111111');                
            });
            
            it("with smaller number then should pad bits to 8 places", () => {
                expect(filter(0x01)).toEqual('00000001');                
            });          
        });
    });
}

Let’s break down what happened. First, I described the test suite (eightbits filter). I provided a variable to hold the instance of the filter. Before each test, I run the module alias. This is provided by angular-mocks.js and enables us to stand up modules for testing. Next, I use the inject method to handle dependencies. $filter is a specific angular service. By passing it as a parameter to the injection method, angular will look at any dependencies wired up so far and provides the service. This service allows me to ask for a filter by name, so when the filter is registered, the injector will pick it up and provide it to me.

Now that I have an instance of the filter, the test conditions play through. When the filter is passed zoo, we want zoo to bounce right back. When it is passed a byte with all bits set, we want that to reflect in the result, and when I pass a single bit set I check for the padding. Of course, we haven’t built the filter yet so all of these tests fail (but you may find it interesting that this will compile, since I’m referencing the filter via the filter service and not as a direct reference).

I can now write the filter – note that the filter registers itself with angular using the same name I used in the test.

module Filters {

    export class EightBitsFilter {
       
        public static Factory() {
            return function(input): string {
           
                var padding: string = "00000000";
                
                if (angular.isNumber(input)) {
                    var result = padding + input.toString(2);
                    return result.substring(result.length - 8, result.length);
                }

                return input;
            }
        }
    }

    Main.App.Filters.filter("eightbits", [EightBitsFilter.Factory]);
}

I am using a factory pattern that provides a way for angular to create an instance of the filter. Angular will call this and keep track of the instance and inject it anywhere it is used. After the definition of the filter, I get a reference to the angular module for filters, and call the filter method. This is passed the name of the filter and its dependencies, which in this case is just the factory to create it. The signature provided by angular is to take in a string and return a string. I check to see if it is a number (otherwise I just return the value, as in the “zoo” case), then I cast it to binary and pad it as necessary.

Here I’ve been able to test a filter that is used in the UI. I was able to provide a spec for it that describes all of my expectations, so anyone looking at the test knows how it is supposed to behave. And, by registering the filter, I no longer have to worry about how bits are exposed by my model. Instead, I simply bind them to the scope and pass them through the filter to appear in the UI.

If you’re not convinced how powerful this feature is, imagine an enterprise customer that requires you to format the dates in a special format. They are still arguing over the exact date format, so instead of waiting or risking refactoring, you build a filter. For now, it simply spits out the date. Down the road, you get the details for how to display it but the date must be formatted differently in various areas of the application. Is that a problem? No! You can easily create a service that represents where the user is, then allow the directive to query the service and format the date accordingly. You make a change in one, possibly two places, and roll out the change, rather than having to search every place in the application you used the date to make an update. Starting to see the value in this?

Many posts cover services, filters, and directives in isolation, so in my next angular-related post I’ll share how to use a service in combination with a directive to create a testable UI artifact that your code can interact with independent of how the UI is actually implemented.

No comments:

Post a Comment