Thursday, February 23, 2012

Async Testing in Dart

Today, I continue exploring unit testing in Dart. I am using the bleeding edge in-browser testing suite, which has worked fairly well so far. It seems as though it is meant to be run right on top of the live application, which could be interesting—add a <script> tag and see the test suite results overlayed on the application.

I have tested most of the Collection aspects of the HipsterCollection class from my Hipster MVC framework. Tonight I hope to cover the Ajax features. First up, the fetch() method. I am not sure how to go about doing this, so I start by verifying that calling fetch() directly on the base class fails (a sub-class needs to define url()):

That test.json file exists. Even if I load it in the test page via script tag, I still get a CORS violation.

I deal with with the same thing when I use Jasmine to test JS applications, but at least with those I can use libraries like sinon.js to fake XHR responses. I cannot do that in Dart without somehow telling my MVC library to use a different XHR object. Luckily, I can do just that.

Based on Backbone.sync, the HipsterSync class allows me to redefine the manner in which storage operations are performed. By default, it uses XHR, but I can swap that out for a no-op:

When I run the test suite now, I see the no-op print() output in the Dart console:

[sync] get / Instance of 'TestHipsterCollection'

And all of my tests are again passing:

They might be passing, but I am not really testing anything other than that the application does not crash. To be sure, there is value in that, but I can do better. The HipsterSync call is an asynchronous callback. Rooting through the bleeding edge test suite, I come across the asyncTest() function. This is not a vows.js-style assertion that a specific callback is invoked. Rather, it provides a mechanism to say that any number of callbacks will be invoked and a way to signify that they were actually called.

In this case, I expect that my sync callback will be invoked once. To verify that it has been called, I invoke callbackDone() inside the sync method:

Actually, that is not so much failing as preventing the suite from completing. That is better than nothing, but it would be preferable to say that, if the proper number of callbacks have not been invoked in a certain amount of time, then the test fails. Something to investigate another day.