Saturday, July 6, 2013

Poor Man's HTTP Test Server in Dart


Yesterday I managed to get a simple Dart test server running for the purposes of unit testing HttpRequest code. Emphasis on the word simple. All that it does is return a JSON string regardless of the request.

Today, I would like to test POSTing and possibly PUTting updates. To accomplish this, I am going to need to be able to persist data in my test server. I could do this in memory, but I have a quick and dirty way to do this already. So I add dart dirty as a dependency (along with UUID for IDs) to the list of dependencies in the library I am trying to test:
name: hipster_mvc
version: 0.2.6
description: Dart-based MVC framework
author: Chris Strom 
homepage: https://github.com/eee-c/hipster-mvc
dependencies:
  unittest: any
  dirty: any
  uuid: any
  js: any
A quick pub update and I have my quick and dirt noSQL database ready to go:
➜  hipster-mvc git:(http-test-server) ✗ pub update
Resolving dependencies.........
Dependencies updated!
➜  hipster-mvc git:(http-test-server) ✗ ls packages 
browser  dirty  hipster_mvc  js  meta  unittest  uuid
I would like to be able to write a test along the lines of:
      test("it can POST new records", (){
        _test(response) {
          expect(response, {'test': 42});
        }

        var model = new FakeModel();
        model.url = 'http://localhost:31337/widgets';
        model.attributes = {'test': 42};

        HipsterSync.call('create', model).then(expectAsync1(_test));
      });
I am creating a fake model which is attempting to persist data in Hipster MVC at the /widgets path on my test server. I expect that when I POST / create data that the server will reply back to me with the contents that I have created:{'test': 42}.

I need to start building out my test server to support this, so I add the necessary import statements along with a handler for the /widgets path:
import 'dart:io';
import 'dart:json' as JSON;

import 'package:dirty/dirty.dart';
import 'package:uuid/uuid.dart';

main() {
  var port = Platform.environment['PORT'] == null ?
    31337 : int.parse(Platform.environment['PORT']);

  HttpServer.bind('127.0.0.1', port).then((app) {

    app.listen((HttpRequest req) {
      if (req.uri.path.startsWith('/widgets')) {
        handleWidgets(req);
        return;
      }

      defaultResponse(req);
    });
  });
}
To handle the /widgets, I need to determine the HTTP verb that is being used to make the request. I also need to know if the request includes an ID or not:
handleWidgets(req) {
  var r = new RegExp(r"/widgets/([-\w\d]+)");
  var id_path = r.firstMatch(req.uri.path),
      id = (id_path == null) ? null : id_path[1];

  if (req.method == 'POST') return createWidget(req);
  if (req.method == 'GET' && id != null) readWidget(req);

  notFoundResponse(req);
}
I will add more over the next few days, but that is a decent start. Now, I need to be able to create widgets in the persisted DB:
createWidget(req) {
  HttpResponse res = req.response;
  Uuid uuid = new Uuid();
  Dirty db = new Dirty('test.db');

  req.toList().then((list) {
    var post_data = new String.fromCharCodes(list[0]);
    var widget = JSON.parse(post_data);
    widget['id'] = uuid.v1();

    db[widget['id']] = widget;

    res.statusCode = 201;
    res.headers.contentType =
      new ContentType("application", "json", charset: "utf-8");

    res.write(JSON.stringify(widget));
    res.close();
  });
}
I am probably trying to do too much in there. I am reading the body of the request for the POSTed JSON data. Then I turn around and fashion a response. At the same time, I am writing to the test DB. As I said, I could probably clean that up, but it will do for now—at least as a test server.

With that, I have my test passing, but I still have a problem: the test DB is sticking around:
➜  hipster-mvc git:(http-test-server) ✗ cat test/test.db 
{"key":"962c9aa0-c9c0-11e2-9027-23f0a97992d8","val":{"test":42,"id":"962c9aa0-c9c0-11e2-9027-23f0a97992d8"}}
I will explore teardown HTTP paths tomorrow, but for now, I manually remove this in my continuous integration script:
echo "starting test server"
dart test/test_server.dart &
server_pid=$!

echo "content_shell --dump-render-tree test/index.html"
results=`content_shell --dump-render-tree test/index.html 2>&1`
# ... 

kill $server_pid
rm -f test/test.db
# ...
That seems a decent stopping point for tonight. Setting up the server and the “routing” was a bit more challenging that I had expected (I keep thinking that I have finally found a use for case statements in Dart only to be proven wrong). Still, this seems promising for testing the remainder of Hipster MVC's RESTful routing methods, which I hope to finish off tomorrow.


Day #804

No comments:

Post a Comment