Wednesday, March 18, 2015

Proper (Async) Testing of Polymer.dart


I have this test. It's not even really a test. It's not making assertions about my very simple <hello-you> element. It just verifies that Polymer.dart is working correctly. It even works when run in Dartium:



Buy my very simple test does not work in headless testing. Stranger still is that it used to work. But in the latest development version of Dart (1.9.0-dev.10.7) and the latest Polymer.dart (0.16.0+3), my simple test will not work headless.

Well, I think it is not working. When I run the test in content_shell, which is nomrally the easiest way to headless test in Dart, I get nothing:
$ content_shell --dump-render-tree ./test/index.html 
...

#EOF
#EOF
#EOF
So what is going wrong here? Is the development version of Dart buggy? Is the newly released Polymer.dart to blame? Is content_shell not longer a good solution for headless testing? The answer is almost certainly that I am doing something wrong, but what?

The actual test that I got working in Dartium last night seems harmless enough:
import 'package:unittest/unittest.dart';
import 'package:unittest/html_config.dart';
import 'package:polymer/polymer.dart';

main() {
  useHtmlConfiguration();

  initPolymer();
  Polymer.onReady.then((_) {
    // Setup to add the element to the page here...

    group("<hello-you>", (){
      test("has a shadowRoot", (){
        expect(
          query('hello-you').shadowRoot,
          isNotNull
        );
      });
    });
  });
}
I import unittest, a unittest HTML formatter, and Polymer. I use the HTML formatter, initialize Polymer, and, as I found last night, I have to wait for Polymer to be ready before making my assertions. In the browser, this works. My <hello-you> element has a Shadow DOM like all web components should. But for whatever reason, there is not even any output from content_shell.

It does little good to whine about the problem. So I start breaking it down. Do simple tests work from content_shell? Let's try testing my sanity:
main() {
  useHtmlConfiguration();

  test("sanity", (){
    expect(1 + 1, 2);
  });
  // Polymer tests below...
}
Surprisingly, that works:
content_shell --dump-render-tree ./test/index.html
...
PASS
1       PASS
Expectation: sanity .

All 1 tests passed
#EOF
#EOF
#EOF
It works, but… why does this new test count as “All 1 tests”? I did not delete the Polymer test. It is not even seeing it. Wait…

Ah, the joys of asynchronous testing. It is super nice that Dart has facilities built-in for elegant asynchronous testing, but it sure would be nice if it would tell you when you're doing something wrong. The problem in this case is nothing in my test is blocking the browser from exiting. I have a Future waiting around to execute if Polymer.onReady ever completes. But for all content_shell knows, this will never complete.

I have to instruct my tests that this is an asynchronous operation. More to the point, I have to find a way to instruct the test runner to block until this asynchronous function is called when Polymer.onReady completes. This is the purview of expectAsync().

The expectAsync() method is a simple wrapper around a function that does just what I need—it blocks the test runner until the expected asynchronous function is called. The more immediate help for me is that content_shell now knows to wait for this asynchronous test to be invoked.

Since expectAsync() is a test matcher, it needs to reside inside a test(). So I have to rearrange my test such that the expectAsync() for Polymer.onReady() is inside the test():
  group("<hello-you>", (){
    var _el;
    setUp((){
      _el = createElement('<hello-you></hello-you>');
      document.body.append(_el);
    });

    tearDown((){
      _el.remove();
    });

    test("has a shadowRoot", (){
      Polymer.onReady.then(expectAsync((_) {
        expect(
          query('hello-you').shadowRoot,
          isNotNull
        );
      }));
    });
  });
That seems like the kind of thing that might make for a nice Polymer.dart test matcher. I may root around to see if one is already defined in Polymer itself. It would be really nice if something in unittest were able to identify this kind of problem. I have definitely seen this before—and probably should have recognized the symptoms sooner. But it would be nice not to have to worry about missing this in the future. Perhaps a better solution is to switch to the better async-supporting scheduled_test library. One way or another, this will impact Patterns in Polymer—even if only the testing chapters. A topic for tomorrow.

Day #2


No comments:

Post a Comment