How to test your angular application within meteor

May 11, 2015

Combining the power of angular with the reactiveness that meteor provides makes development a lot easier.
There currently is no documentation on how to test this configuration.
Let me introduce you to making your applications robust and clean by writing unit tests.

In this tutorial I am going to be testing a small controller for a chat application.
We want to be able to:

  • receive messages from meteor
  • save a new message

Before we can do anything we need to setup our environment.

Installing meteor

Follow the instructions for your OS on the official meteor website.

Installing angular

meteor add urigo:angular angular:angular-mocks

First we will need to install angular and angular mocks. Integrating angular has been made incredibly simple.

Installing velocity

meteor add sanjo:jasmine velocity:html-reporter

Velocity is the testing framework of choice when it comes to meteor. I am going to be using it with jasmine.

Running the meteor application

With velocity and angular installed run your app and open your browser.
You will notice that the velocity interface will appear on the right.
velocity

Notice that velocity indicates that there are no tests. Let’s generate a simple skeleton.

Generating tests

generate client tests These tests will be executed automaticly due to velocity’s reactive nature.

You might notice that a new instance of your browser has opened. This is where your tests will be executed. If you want to hide the browser instance. Run meteor with the JASMINE_BROWSER environment variable.

JASMINE_BROWSER=PhantomJS meteor run

Writing our first test

Create a new file called test.js under tests/jasmine/client/unit.

First we have to create the module and get the controller.
This is standard angular boilerplate test code but notice that I use messages.
Check out the implementation of the message service here.
I like to wrap my $meteor calls inside a service which makes testing a bit easier.

//test.js
describe('MessageCtrl', function () {
    beforeEach(module('app'));

    /*
     * Get a new controller before each test is executed
     */
    var $controller = {}, messages = {}, $scope = {};
    beforeEach(inject(function (_$controller_) {
        $controller = _$controller_;
    }));

    /*
     * Messages will be used to make mocking of the actual Messages easier
     */
    beforeEach(function () {
        messages = {
            getCollection: function () {
                return [];
            },
            insert: function () {

            }
        };
    });
});

Now we are ready to test the controller. Let’s take a look at our first test.

//test.js
it('should have messages', function () {
    var expected = [{_id: 1, text: 'my message'}];
    spyOn(messages, 'getCollection').and.returnValue(expected);
    $controller('MessageCtrl', {
        $scope: $scope,
        messages: messages
    });
    expect($scope.messages).toBe(expected);
});

There is one message in our fake database. We expect it to be available in the controller through$scope.messages.

Watch velocity run the test and you will see that the test fails. This is because the module does not exists yet. So let’s make one in a file that is accessible by our client.
Let’s call this file app.ng.js.

/*
* app.ng.js
* Don't forget the `angular-meteor` dependency. Angular needs it to communicate with meteor.
*/
var app = angular.module('app', ['angular-meteor']);

Now we have to define the controller.

//app.ng.js
app.controller('MessageCtrl', function ($scope, messages) {
    $scope.messages = messages.getCollection();
});

We have defined the controller and because we are using getCollection the first test passes!
The only thing we have to do now is to make sure that the message can be saved.

//test.js
it('should be able to insert a message', function () {
    spyOn(messages, 'insert');
    $controller('MessageCtrl', {
        $scope: $scope,
        messages: messages
    });
    $scope.insert();
    expect(messages.insert).toHaveBeenCalledWith($scope.text);
});

We need to add the insert function to the scope of the controller. That function has to call messages.insert with some text.

//app.ng.js
app.controller('MessageCtrl', function ($scope, messages) {
    $scope.text = '';

    $scope.messages = messages.getCollection();
    $scope.insert = function () {
        messages.insert($scope.text);
    }
});

That’s it! We have a functional controller and two tests to make sure it works.
Have a look at the repository for the entire code.

conclusion

The reactiveness of meteor makes development a lot faster but it is nice to stand still and appreciate the value of testing.

I am really excited about meteor and will probably post a lot more about it.
Subscribe to my rss feed if you want to be notified whenever I publish a new post.
Thank you for reading.

Comments