Monday, July 29, 2013

Better Scheduled Tests in Dart


I got a start on scheduled_test last night. The scheduled test package is a replacement for the unittest library that is built into Dart. I have grown rather fond of unittest's ability to test asynchronous code, but the scheduled test package suggests that it may be even better, so I cannot resist.

One difference that I have found is that solo_group() does not work for running a single group of tests (as it does in unittest), though solo_test() does work like usual. Aside from that, I am mostly trying to wrap my brain around scheduling asynchronous tests. I think last night's scheduled test may not be the Scheduled Test Way™:
    test("POST /stub responds successfully", (){
      schedule((){
        new HttpClient().
          postUrl(Uri.parse("http://localhost:31337/stub")).
          then((request) {
            request.write('{"foo": 42}');
            return request.close();
          }).
          then(wrapAsync(
            (response) {
              expect(response.statusCode, 204);
            }
          ));
      });
    });
It works, but it is still not pretty. Once code is pretty, then I feel like I am doing it “right”—or at least the Scheduled Test Way™.

The first thing that I think to try is splitting the expectation out from the test execution code. Currently, it is a little hard to quickly scan to find the expectation, though at least the expectation is at the bottom of the code. In scheduled tests, different asynchronous code—like execution and response expectation—is split into different schedules:
    test("POST /stub responds successfully", (){
      var response;

      schedule(() {
        return new HttpClient().
          postUrl(Uri.parse("http://localhost:31337/stub")).
          then((request) {
            request.write('{"foo": 42}');
            return request.close();
          }).
          then((res) => response = res);
      });

      schedule(()=> expect(response.statusCode, 204));
    });
I quite like that. I have to introduce a test-scoped variable, response, but aside from that, it reads rather nicely.

The Scheduled Test documentation (which is excellent) suggests that improvement is still possible. Specifically, that test scoped variable is unlikely to scale well. Instead, the Scheduled Test Way™ is to obtain the result of a previous scheduled to be used in a subsequent schedule:
    test("POST /stub responds successfully", (){
      var responseReady = schedule(() {
        return new HttpClient().
          postUrl(Uri.parse("http://localhost:31337/stub")).
          then((request) {
            request.write('{"foo": 42}');
            return request.close();
          });
      });

      schedule(() {
        responseReady.then((response)=> expect(response.statusCode, 204));
      });
    });
The responseReady variable is a future. The Scheduled Test documentation says that the returned future will complete with the return value. Here, I do something a bit different, but it still works. I return the result of postUrl()'s then(). When you post a URL, then write and close the request, you get another future back that completes with the response. So the return value of the then() in the above code is a future of type HttpResponse (Future<HttpResponse>).

Even though it is not explicitly stated in the Scheduled Test documentation, returning this future will also work when used in the next scheduled test. And it reads quite nicely as well: when the response is ready, then I expect that the response status code will be 204.

I rather like that. I wonder if there are any other cool features to investigate?

Day #827

No comments:

Post a Comment