MeteorJS stands out among the recent big Javascript frameworks with its radical view on creating web applications. It seamlessly connects the client side code with a Node + MongoDB based backend and promises unbelievably fast application development.
As an introduction to Meteor.js, I set out to build a simple but properly tested web application and share what I learned.
Installing the Meteor.js framework
Meteor has a one line install process based on a script from its website:
sh
$ curl https://install.meteor.com | /bin/sh
The above command installs all the needed dependencies along with a MongoDB database. After this you are ready to start writing applications.
Creating an example Todo application
sh
$ meteor create todos
Right after the above command finishes you can launch a working web application with the following:
sh
$ cd todos
$ meteor --port 3000
=> Meteor server running on: http://localhost:3000/
Unit tests
Meteor works with various testing frameworks and test runners. One of the most popular test runners is Velocity that supports Javascript test frameworks like Jasmine and Mocha.
Jasmine — my favorite Javascript test environment — is easy to set up with Meteor and Velocity.
sh
$ meteor add sanjo:jasmine
In Meteor you should put all your tests in a tests directory. Before you start writing your own tests, first create a directory structure like the following one, as required by sanjo:jasmine.
sh
$ mkdir -p tests/jasmine/server/unit
$ mkdir -p tests/jasmine/client/integration
Velocity also offers a nice HTML reporter that overlays your application and shows a red/gray circle in the upper right corner of the application.
When you click on the circle it will list all the applications test statuses.
You can install it with the following command:
sh
$ meteor add velocity:html-reporter
Unfortunatelly, in the moment of writing this blog post, there is no standard way to run Velocity tests from the command line. A Node package velocity-cli is trying to change that, at least until there is no meteor test command.
Integration and acceptance tests
For integration tests you can use Nightwatch which will run Selenium-based browser tests on your application. To install it for your application, run:
sh
$ meteor add clinical:nightwatch
Unfortunately, the setup for the rest of the framework is a little painful. First you need to install npm and node, and java runtime.
sh
$ sudo apt-get install nodejs
$ sudo apt-get install openjdk-7
After which you should execute the following installation steps for nightwatch.
sh
$ ln -s .meteor/local/build/programs/server/assets/packages/clinical_nightwatch/launch_nightwatch_from_app_root.sh run_nightwatch.sh
$ sudo chmod +x run_nightwatch.sh
$ sudo ./run_nightwatch.sh
Your integration tests should be placed inside tests/nightwatch directory.
Writing a todo application
To demonstrate the power and expressiveness MeteorJS, I will walk you through the design process of a simple Todo application. This application will have only two features. It will show all todos a user saved in the database, and it will allow us two add more todos.
The server and the client in a MeteorJS application are interconnected, and as a matter of fact, can be even defined in the same file. Let’s create a file named todos.js in the projects root and initialize a Mongo model that can store todos for our application.
javascript
Todos = new Mongo.Collection("todos")
In order to handle and check the validity of newly created todos, a class that represents a Todo can come quite handy. Right under the definition of the Todos collection we can create a Todo class.
javascript
function Todo(name) {
this.name = name;
}
Todo.prototype = {
valid: function() {
return this.name && this.name != "";
},
save: function() {
Todos.insert({name: this.name});
}
};
As a good TDD practitioner we should also create a matching test using Jasmine. We can define a test inside the tests/jasmine/server/unit/todo_spec.js file. At the moment we’re still curious and learning, hence implementation first.
javascript
describe("Todo", function() {
beforeEach(function() {
this.name = "wash dishes"
this.todo = new Todo(this.name);
});
it("accepts name", function() {
expect(this.todo.name).toEqual(this.name);
});
describe("#save", function() {
beforeEach(function() {
spyOn(Todos, "insert");
this.todo.save();
});
it("inserts into the database", function() {
expect(Todos.insert).toHaveBeenCalled();
});
});
});
Filling the database on startup
On application startup we can invoke various actions. For example we could prefill our database with Todos if there is no todo defined.
Put the following in the todos.js file.
javascript
function prefillTodos() {
if(Todos.find().count() !== 0) return;
var todos = ["Create a Todo", "Learn MeteorJS"];
todos.forEach(function(todo) { new Todo(todo).save(); });
}
if (Meteor.isServer) Meteor.startup(prefillTodos);
And its tests in the tests/jasmine/server/unit/prefill_todos_spec.js file.
javascript
describe("prefillTodos", function() {
beforeEach(function() {
spyOn(Todos, "insert");
spyOn(Todos, "find").and.returnValue({count: function() { return 0; }})
prefillTodos();
});
it("initializes the database with default todos", function() {
expect(Todos.insert).toHaveBeenCalledWith({name: "Create a Todo"});
expect(Todos.insert).toHaveBeenCalledWith({name: "Learn MeteorJS"});
});
});
Templates
MeteorJS uses templates to display information to the user. To create a HTML view for the todos, first create a todos.html in the project root and create a todo view like the following.
html
Welcome to Meteor!
{{> todos}}
{{#each todos}}
{{> todo}}
{{/each}}
{{name}}
This file contains regular HTML notation, spiced up with some curly braces. These curly braces are Handlebars-like constructs and allow us to dynamically insert data in our Html views.
Each template can receive arbitrary JSON data that can be used to parameterize the output of that template. To invoke the rendering of a template MeteorJS uses the {{> templateName}} notation.
Each of these templates can have a helper or an event handler.
Helpers are handy methods that we can invoke inside the definition of our templates. Event handlers let us react to various Javascript events. The following code segment shows how to set up a helper method for our todos template.
Append the following snippet to the todos.js file.
javascript
function setUpTodoTemplates() {
Template.todos.helpers({
todos: function () {
return Todos.find();
}
});
}
if (Meteor.isClient) setUpTodoTemplates();
Testing templates
In MeteorJS there is a custom of testing templates with Jasmine. We should start with testing our todos template in the tests/jasmine/client/integration/todos_template_spec.js file.
javascript
describe("TodosTemplate", function() {
beforeEach(function() {
var div = document.createElement("div");
var data = [{name: "test1"}, {name: "test2"}];
var comp = Blaze.renderWithData(Template.todos, data);
Blaze.insert(comp, div);
});
it("contains all the passed todos", function() {
expect($(div).children().length()).toEqual(2)
});
});
Creating new todos
Combining the above knowledge, we can easily extend our todo application with a form that creates new todos.
We should start with a template in our todos.html file.
html
Now, insert a template render invocation inside our todos template.
{{#each todos}}
{{> todo}}
{{/each}}
{{> createTodo}}
To handle button clicks from out createTodo template, we should add an event handler inside the setUpTodoTemplates. The following code snippet registers an event handler that will create a new Todo.
Put the snippet inside the setUpTodoTemplates in the todos.js file.
javascript
Template.createTodo.events({
'click button': function() {
var input = $("#newTodo");
var name = input.val();
input.val("")
new Todo(name).save();
}
});
An acceptance scenario for the above action could be written as the following inside a tests/nightwatch/creatingTodos.js file.
javascript
module.exports = {
"Adding a Todo" : function (client) {
client
.url("http://localhost:3000")
.waitForElementVisible("body", 1000)
.pause(10000)
.assert.visible("input[type=text]")
.assert.visible("button")
.setValue("input[type=text]", "Test Todo")
.click("button")
.assert.containsText("body", "Test Todo")
.end();
}
};
To run this end-to-end test run the following.
sh
$ sudo bash run_nightwatch.sh
Final screenshot and source code
You can clone the source code for the whole todo application from my Github repository.
Summary
Meteor is an exciting new technology that allows us to create eventful application with ease. The Meteor platform seems to be quite stable, however the testing libraries seem to be lagging behind and there is a lack of good documentation.
I encountered several stumbling blocks while working with tests, when it was hard to find any information on the issue. For example incompatibilities between Nightwach and the version of Firefox I was using, running integration tests for the client side sometimes unexpectedly failed with an error or exception. Fortunately each of the test libraries are under heavy development and I am expecting high level of test integration in the near future.
Finally, here is a list of articles that helped me understand Meteor better.
– MeteorJS Homepage
– Bulletproof MeteorJS
– Setting up selenium and nightwatch