Tuesday, January 15, 2013

Testing Isolates in Dart

‹prev | My Chain | next›

Isolates in Dart are a means for separating long running processes from the main thread of execution. I do not cover them in great detail in Dart for Hipsters. I do include a simple example. Since I have an example, I need to test that example.

The isolate example's calling context looks something like the following:
main() {
  SendPort sender = spawnFunction(findDoom);

  print('Certain doom awaits...');

  var year = 2013;
  sender.call(year).then((message) {
    print("Doom in $year is on a $message.");
  });
}
I send to the long running findDoom() method, which I have spun up in its own spawnFunction() isolate. Testing of the calling context is a little tricky because of the asynchronous nature of the entire operation—who knows how long that findDoom() isolate might take to return? Also complicating things are those two print() methods. I have dealt with a single print statement in the past by overriding Dart's built-in print() method and testing the value printed. I could do something similar here, even with two print() statements instead of one.

But then I get to thinking. What I really want to test here is that the isolate is running properly and returning a proper future value. I already know from last night how to test future values. The result of sender.call(year) in my code example is a future, so I write a test that matches the expectation of that future:
library isolate_test;

import 'package:unittest/unittest.dart';

import 'dart:isolate';
import '../main.dart' as Isolate;

run() {
  group("[isolates]", (){
    test('can send back replies', (){
      SendPort sender = spawnFunction(Isolate.findDoom);
      expect(sender.call(2013), completion("Thurs"));
    });

  });
}
The test itself states the expectation of calling the long-running isolate with a value of 2013 is a future that will eventually complete with the value of Thursday (see the Doomsday Rule for the explanation of why). That is beautifully compact testing to express a fairly complex operation.

To make that work, I import dart:isolate (otherwise I would not have the spawnFunction() method). I also import my main source file with the Isolate prefix so that I don't have to worry about name clashes.

After that, it just works:
➜  isolates git:(master) ✗ dart test/test.dart
unittest-suite-wait-for-done
PASS: [isolates] can send back replies

All 1 tests passed.
In keeping with my recent practices of not believing that testing something so complicated could be so easy, I intentionally break the expectation:
➜  isolates git:(master) ✗ dart test/test.dart
unittest-suite-wait-for-done
FAIL: [isolates] can send back replies
  Expected: 'Fri'
       but: was 'Thurs'.
So I would seem to have a legitimate two-line test that verifies the result of a long running isolate process. I love it!


Day #631

No comments:

Post a Comment