Thursday, December 27, 2012

Complete Full Stack Testing in Dart

‹prev | My Chain | next›

As I have struggled with the right balance of mocking and stubbing in my Dart tests, it occurred to me that maybe the answer is no stubbing at all. The obvious problem with HttpRequest code is the dreaded cross origin violation:
XMLHttpRequest cannot load file:///comics. Cross origin requests are only supported for HTTP.
It would be ideal for a headless browser test to also be able to spin up a backend server against which it could run its tests. That is a ways off, however since the dart:io library is not available in Dartium (meaning I cannot spin up a web server in a browser, which is just as well).

Manually spinning up a web server and then accessing the test from the web server does not particularly appeal to me, but I file this under "don't knock it until you've tried it."

So I update my application server to serve up the test directory as well:
main() {
  HttpServer app = new HttpServer();

  new StaticFiles(app);
  new StaticFiles(app, dir:'test', mountPoint: '/test');
  // ...
})
Already I dislike this approach—including the test directory in a production app is just wrong. Actually, I already have a test environment check in place (to listen to a different port). What if I include that test directory only if running in test mode?
main() {
  HttpServer app = new HttpServer();

  new StaticFiles(app);
  // ...
  var port = 8000;
  if (Platform.environment['ENV'] == 'test') {
    port = 9999;
    new StaticFiles(app, dir:'test', mountPoint: '/test');
    Comics.filename = 'test.db';
  }
  // ...
}
That works just fine and I still have all of my (stubbed) tests passing, now on the 9999 test port:


After this, it is a matter of getting the client-side code built up enough to make requests to the backend. This code is primarily intended to support a chapter from Dart for Hipsters and had been scattered to support easy inclusion into the book. Now that my emphasis is on tested code samples, I have to work to reassemble that old code.

Eventually, I settle on the following setup code:
import '/scripts/comics.dart' as Main;

run() {
  group("[main]", (){
    var el = new Element.html('<ul id="comics-list">');

    setUp((){
      var comics = new Main.ComicsCollection();
      comics.create({
        "title": "Sandman",
        "author":"Neil Gaiman"
      });

      document.body.append(el);
    });
I directly access the collection class in the code that I am testing, using it to create a record on the test server. Then, in the test, I verify that this record is used to build the application page:
    test('populates the list', (){
      Main.main();
      window.setTimeout(() {
        expect(el.innerHtml, contains('Sandman'));
      }, 100);
    });
Unfortunately, I find that the setTimeout is required to give the page a chance to render after receiving the response from the server. That aside, it works as desired.

There are some definite advantages to this very full stack testing. I had a lot of code missing until I had to get it working end-to-end like this. Still, I do not think that I am sold on this approach. In addition to the setUp, I also have to be careful to tearDown my backend DB. That is an easy thing to lose track of. And I just don't care for having to spin up the test server and remember the URL to access. It is much easier to be in the right directory and type chrome index.html. That said, the full-stack effort is of some value, so I may find some way to make use of it in the future.

Day #612

1 comment: