TDD + ES6 + AngularJs (Part II of II)

In the previous blog entry I introduced a simple todo-app developed with AngularJs and ES6. In this entry I will be reviewing the tests written using ES6 for this application.

This is how an AngularJs unit tests looks like using ES6, Jasmine, Sinon, Chai and some additions that I'll explain to you later.

import TodoService from '~/src/app/components/todo-service';
import Todo from '~/src/app/model/todo';

describe('Todo service', () => {

  const TODO = 'todo';
  let todoService, restService, rootScope, q;

  beforeEach(angular.mock.module('todo-app'));

  beforeEach(angular.mock.inject(($injector, $rootScope, $q) => {
    restService = $injector.get('restService');
    todoService = new TodoService(restService, $q);
    rootScope = $rootScope;
    q = $q;
  }));

  it('loads the list of todos', () => {
    given:
    const todo1 = { id: 1, content: 'Buy milk', completed: true };
    const todo2 = { id: 2, content: 'Buy beer', completed: true };
    const response = q.when([todo1, todo2]);
    const loadTodosAction = sinon.stub(restService, 'list').returns(response);
    const todos = [new Todo(todo1), new Todo(todo2)];

    when:
    todoService.loadTodos();

    then:
    chai.expect(loadTodosAction).to.have.been.calledWith(TODO);

    and:
    rootScope.$apply();

    then:
    chai.expect(todoService.todos).to.be.deep.equal(todos);
  });
});

Jasmine uses describe blocks to declare tests suites and it blocks to define the content of a test.

At the beginning of the test, the first beforeEach reference loads the application and the second one creates an instance of the System Under Test (the todo service), injecting its dependencies.

In this blog post I will discuss only about this test for the sake of brevity, but you can take a look at the full tests suite in the github repository todo-app-frontend.

This test checks that the todoService is able to load a list of todos making a call to its collaborator the restService to achieve that goal.

As you can see, I am using Sinon to create a stubbed version of the restService.list() method. I know that when the todo service loads the list of todos, it will make a call to this method, and this method will return a promise that it's going to contain an array with a list of todo JSON objects.

So when todoService.loadTodos() method is invoked, it has to be verified that the stubbed method on the restService collaborator has been called. Since this method returns a promise, we need to resolve it. To do that, we are using angular rootScope.$apply() and then, we can verify that a list of Todo domain entities has been created in the System Under Test based on the returned values from the restService collaborator.

In order to check that the interaction with the stubbed method has happened and the content of the todoService.todos property contains the expected list of todos, I am using the Chai assertion library.

As you might have noticed, this test code is delimited by different given, when, then blocks. There are testing frameworks from other programming languages that use this approach, and I think it improves the readability of the code. If you are wondering how is this possible, JavaScript has the concept of tags (a feature that usually is not know) and it is what I am using here.

Unit tests are executed with Karma and a plugin called Istanbul is used to generate a coverage test report. It was tricky to have ES6 test code working with the test coverage plugin and I am not going to enter into the details here, but you can review the karma.conf.js and package.json files to have a better understanding of how this is done.

Coverage test report

Now let's take a look at an integration test

import _ from 'lodash';
import fixtures from '~/src/fixtures/fixtures';
import httpMock from '~/spec/integration/http-mock';

import Todo from '~/src/app/model/todo';

describe('Todo app controller', () => {
  let controller;
  const todos = _.map(fixtures.todos, (todo) => new Todo(todo));

  beforeEach(angular.mock.module('todo-app'));
  beforeEach(httpMock.init());
  afterEach(httpMock.reset());

  beforeEach(angular.mock.inject(($controller) => {
    controller = $controller('TodoController');
  }));

  it('deletes a todo', (done) => {
    controller.loadTodos()
      .then(() => controller.delete(_.first(controller.todos)))
      .then(() => {
        chai.expect(controller.todos).to.be.deep.equal(_.tail(todos));
        done();
      }
    );
  });
});

Here I am creating an integration test for the Todo Controller, mocking external dependencies like http calls to the backend service. In order to do that a fake http service is set up with pre-programmed responses, httpMock is an utility class that has been created with the aim to encapsulate this logic.

You can notice how the instance of the controller in the last beforeEach block is created, it is different from what it is done in the unit test. In this case, the instance of this controller will have all of its dependencies automatically injected by the framework.

The 'deletes a todo' test, loads a list of todos, and then it deletes the first todo of that list and checks that the final list of todos does not contain the deleted one. Since all of this code is asynchronous I have to use the done function that Jasmine provides to let the testing framework know when the test has been completed.

Since this is an integration test, it makes sense to execute it against the real backend and check that the integration works as expected, I have created a solution in which these tests can be run in different modes, for example when the local mode is used, there are no real http calls to a backend, and when the dev mode is used the integration tests will make real http calls to an instance of a todo-app-backend server. This can be done executing gulp test:integration:local or gulp test:integration:dev.

Finally let's take a look at one of the Protractor browser tests. In this case, there are two different files, one containing the Page Object and another one with the test case.

class MainPage {
  constructor() {
    this.title = element(by.css('h1'));
    this.todos = element.all(by.repeater('todo in todoController.todos'));
  }
}

export default MainPage;
import Main from '~/spec/e2e/main.po';

describe('The main view', () => {

  const page = new Main();
  browser.get('/index.html');

  it('displays the list of todos', () => {
    expect(page.title.getText()).toBe('todos');
    expect(page.todos.count()).toBe(2);
  });
});

The page object declares the Web page elements that the test needs to interact with, in this case title and the todos list. It uses some selectors to access them, one that is based on css selectors (for the title) and another one that is specific to AngularJs applications using the view model to access a list of todo items.

Finally the test, navigates to the index.html page to then check that the title element of the page contains the word todos and the initial list of todos contains two items by default.

The full test suite can be found on my github repository todo-app-frontend and you can follow me on twitter @doktor500 to find out more.