Unit Testing with Knockout.js

24.10.2011 03:04Comments
Knockout is an MVVM framework for JavaScript. When using an MVVM framework, you have to deal with your model, the views and the “view model”. A view model is a class/object that handles the logic of the view. So if something has to be visible given a certain condition, that’s handled by the view model. Knockout handles the binding of the view model to an HTML view. Note: I am not going to get into the whole obtrusive/unobtrusive discussion, but I have to say that I didn’t quite like what I’ve seen in some of the samples around. So I decided to put up a knockout sample with what I think to be a better approach (a TDD, logic-free-binding approach).

Localtodo

For the sake of learning knockout using TDD and at the same time building something to compare with backbone.js (and also because it’s cool), I asked Jérôme Gravel-Niquet permission to take his localtodos sample built in backbone.js, and port it to see how it would look in knockout. Here’s how the sample looks: image Live demo: http://localtodos.herokuapp.com Live Test: http://localtodos.herokuapp.com/tests.html Source code: https://github.com/machadogj/localtodos-ko

Testing with Knockout

One of the huge advantages of using an MVVM pattern, is that you can test the logic of how a view/form is supposed to behave, without actually going through all the trouble of actually testing the view itself. In my opinion, if you are not going to do unit testing on the view model, you are not going to get the most out of knockout. Contrary to what I thought, unit testing JavaScript is dead simple; I used qunit and it just simply works. Here is an example of a test that asserts that new tasks are not completed:
  1. test("when creating a task, it is not completed.", function () {
  2.     var t = new knocklist.Task();
  3.     //assert
  4.     ok(!t.completed());
  5. });
One of the reasons why these tests are so easy, is that when working with Knockout you don’t have to handle HTML. When you run the tests (browsing the tests.html file) you’ll see something like this: image

Classes

I liked how it felt to do my view model with “classes”. I thought of the different parts of the screen as different classes, and then had them combined in a bigger view model. This way the code under test is a much smaller. Here is a class for the portion of the view that is a form for adding tasks:
  1. var NewTaskModel = function (backlog) {
  2.     this.backlog = backlog;
  3.     this.name = ko.observable();
  4.     this.toTask = function () {
  5.         returnnew todos.Task(this.name(), false, this.backlog);
  6.     };
  7.     this.clear = function () {
  8.         this.name("");
  9.     }
  10.     this.save = function () {
  11.         var task = this.toTask();
  12.         this.clear();
  13.         return task;
  14.     };
  15.     this.hasName = ko.dependentObservable(function () {
  16.         returnthis.name() && this.name().length > 0;
  17.     }, this);
  18. };
I liked how it felt to use “this” to reference the objects properties. There’s one catch though. When using dependentObservables, you have to pass the context as the second parameter for “this” to make sense. Something like:
  1. this.hasName = ko.dependentObservable(function () {
  2.     returnthis.name() && this.name().length > 0;
  3. }, this);

Data-bindings

Avoid adding logic to your data-bindings. This is the one thing that people that usually don’t like knockout will argue, and it’s wrong anyway. Putting logic into your data bindings maybe a quick dirty trick to get going fast, but it’s not a good practice. And what’s even worse, it’s not (easily) testable. To show a quick sample of what not to do:
  1. <buttondata-bind="click: registerClick, enable: !hasClickedTooManyTimes()">Click me</button>
Wouldn’t “canClick” be better for a UI binding than “!hasClickedTooManyTimes()” ? Avoid doing bindings that reference a method on a property, for example:
  1. <buttondata-bind="click: loginForm.signIn">Sign in</button>
Knockout will pass the attached View Model as the context to that method. Basically it means that the meaning of “this” inside of “signIn” method will not be “loginForm” but the object that has the loginForm property. This will usually not make your unit tests to fail, so don’t do that; but when you move to the HTML you will notice that it’s not working properly. I found the “with” data bind in the knockout 1.3 beta to be extremely useful for avoiding the previous problem.

Multiple View Models per Page

When working on knockout, many people is used to create one View Model per page. In my opinion this is fine if the page is very simple, however if the page is a little bit complicated, or has multiple different forms in it, I think it’s very advisable to split that big view model into multiple different view models. Knockout has different ways in which you can do this. The most simple case would be to attach one of the smaller view models to a specific HTML dom element. You can do this with the applyBinding method:
  1. <scripttype="text/javascript">
  2.     var currentBacklog = new Backlog();
  3.     ko.applyBindings(currentBacklog.newTask, document.getElementById("newCurrentTask"));
  4. </script>
When doing this, you can test the “newTask” class without having to worry about Backlog or the rest of the classes.

Pub/Sub Pattern

Multiple views in one page looks good, but there’s one catch though, what if you need to reference a list of tasks in Backlog class, from within the NewTask class? Pay attention to the “save” function:
  1. <scripttype="text/javascript">
  2.     var Backlog = function () {
  3.         this.newTask = new NewTask(this);
  4.         this.tasks = [];
  5.     };
  6.     var NewTask = function (backlog) {
  7.         this.backlog = backlog;
  8.         this.name = ko.observable();
  9.         this.save = function () {
  10.             this.backlog.tasks.push(this.name());
  11.             this.name('');
  12.         };
  13.     };
  14. </script>
While this is possible, and 100% testable, it is making your NewTask and Backlog quite coupled. For simple straight forward scenarios this might be ok, but when making a more complex or composite apps, this can get pretty complex and fragile pretty fast. One thing that I found extremely useful for tackling this problem is the pub/sub pattern. Where basically you expose events, you subscribe to these events, and then you publish events. So suppose that the NewTask class instead of accesing directly the backlog, it exposes an event called “taskAdded”; so that everytime that the save function is invoked, a “taskAdded” event is published with the name as the data. At that point, Backlog class could subscribe to this event, and be the one in charge of adding the task to the list. There are many implementations of pub/sub in javascript, you can use whichever you want. Notice how by using an event aggregator for pub/sub NewTask does not depend on Backlog anymore:
  1. <scripttype="text/javascript">
  2.     var Backlog = function () {
  3.         this.newTask = new NewTask();
  4.         this.tasks = [];
  5.         var that = this;
  6.         eventAggregator.Subscribe("taskAdded", function (name) {
  7.             that.tasks.push(name);
  8.         });
  9.     };
  10.     var NewTask = function () {
  11.         this.name = ko.observable();
  12.         this.save = function () {
  13.             eventAggregator.Publish("taskAdded", name);
  14.             this.name('');
  15.         };
  16.     };
  17. </script>
 

Tests

Naming my tests properly has helped me a great deal when refactoring and/or porting code from one project to another. I found out that it’s better to name your tests in a way in which specifies the behavior of the view and not the implementation of your view model. Here’s what I think to be a very useful test for me:
backlog: when cleaning a completed task, backlog's uncompleted task is not deleted.
This other test on the other hand is not so useful:
newTaskModel: clear sets the name to empty string.
If you go to the UI you will see that there’s no “Clear” button for creating tasks. This was meant to test a method that is used when creating tasks. Even though these tests can be very helpful for us to exercise our code, when it comes to reading the specifications it can be confusing. So as I was writing this blog, I realized that I would rather have a test that says:
newTaskModel: after a task is saved, the name field should be cleared.

Conclusion

I consider Unit Testing to be a good practice regardless of the language. When writing Unit Tests you have to do code that’s well decoupled and well interfaced for your tests to be easy. When developing your applications with Unit Tests in mind, It’s important to pick the right patterns. The MVVM pattern turns out to be an excellent pattern for decoupling your view logic from the view representation; thus taking the unit tests as close of the UI as possible without testing the UI directly. In my opinion Knockout is a pretty cool MVVM implementation, so it’s a perfect fit for unit testing.

comments powered by Disqus