Saturday, March 21, 2015

Scheduled Polymer and Angular (Dart) Tests


I continue to evaluate Patterns in Polymer against the latest pre-release version of Polymer.dart (0.16). Last night I verified that Angular.dart solution from the book still works. Previously, I verified that simple testing still works. Tonight, I hope to verify that testing with Angular.dart works—thus verifying that more complex testing is still working with Polymer.dart 0.16.

I'm fond of scheduled_test for testing asynchronous Dart code. It sits right on top of the standard unittest, so most of the usual methods and matchers work. What it adds to unittest is nicer support for scheduling otherwise asynchronous actions in order, making tests for this type of code deterministic.

The scheduled_test version of the simple, Polymer 0.16 test from the other day is:
library angular_test;

import 'package:scheduled_test/scheduled_test.dart';
import 'package:unittest/html_config.dart';
import 'package:polymer/polymer.dart';

main() {
  useHtmlConfiguration();
  initPolymer();

  var _el;
  group("<x-pizza>", (){
    setUp((){
      schedule(()=> Polymer.onReady);

      schedule((){
        _el = createElement('<x-pizza></x-pizza>');
        document.body.append(_el);
      });

      currentSchedule.onComplete.schedule(() => _el.remove());
    });

    test('has a shadow root', (){
      schedule(() =>
        expect(query('x-pizza').shadowRoot, isNotNull)
      );
    });
  });
}
I import the testing, test output formatter, and Polymer packages. In the main() entry point, I use the test formatter and initialize Polymer, then I start my test. In setUp(), I have two schedules. The first returns the Polymer.onReady future. The scheduled_test library will wait until this future completes before running any other schedules. So, in setUp(), the custom <x-pizza> Polymer element will not be added to the test page until Polymer is ready to process it—perfect! The last bit of code in setUp() is the scheduled_test version of a tear-down block—it removes the custom element after the test is executed.

As for the test itself, it schedules the expectation that the shadow DOM of the custom element is present—a very basic sanity check that Polymer is working. It is important that this test wrap a schedule. Without it, this test will run before the setUp() schedules, which are placed into a queue. In other words, omitting the test schedule would evaluate expectation before Polymer was ready and before the element had been added to the page.

That test passed, but what about a Polymer test on an Angular page? The Angular.dart package contains a lot of testing infrastructure, but I find it hard to use for application testing. It seems of more benefit for testing Angular itself, which is its purpose after all.

So I create an entire application in my test:
    group("angular", (){
      XPizzaComponent xPizza;

      setUp((){
        schedule((){
          var app = new PizzaStoreApp()
            ..bind(NodeBindDirective)
            ..bind(RouteInitializerFn, toValue: storeRouteInitializer);

          applicationFactory()
            .addModule(app)
            .rootContextType(Pizza)
            .run();
        });
        // More schedules here...
      });
      // Tests here...
    });
The PizzaStoreApp() and router are dumbed-down versions of the ones used in the actual application. It is probably a mistake on my part, but my router actually points to a separate page in my test directory:
void storeRouteInitializer(Router router, RouteViewFactory views) {
  views.configure({
    'custom-pizza': ngRoute(
        defaultRoute: true,
        view: 'custom.html'
      )
  });
}
I am purposely trying to test that my Polymer element works when used in an Angular route, so I need something like this, but I am unsure if this is the best way to go about doing so. If nothing else, this requires me to wait for the custom.html partial to be loaded and inserted into the view. And I have no idea how to do this in Angular.dart, so I cheat:
    group("angular", (){
      setUp((){
        schedule((){ /* Schedule Angular app setup */ });o
        schedule((){
          var _completer = new Completer();
          new Timer(new Duration(milliseconds: 50), ()=> _completer.complete());
          return _completer.future;
        });
        // ...
      });
    });
If this were AngularJS, I could wait for a $viewContentLoaded event before completing this schedule. Unfortunately, there is no Anguar.dart equivalent for this event. So I am forced to wait some arbitrary amount of time. Ew.

Ugliness aside, my tests still work. I can verify that an Angular value bound to my Polymer element is updated in response to a change in the Polymer element:
      test('can double-bind to Polymer properties', (){
        schedule(()=> xPizza.addWholeTopping('green peppers'));
        schedule((){
          var angular_el = document.query('pre');
          expect(
            angular_el.text,
            contains('"whole":["green peppers"]')
          );
        });
      });
The xPizza object is a Page Object that makes testing interaction a little clearer in my tests. In the end, these (and other) tests pass with the latest Polymer.dart:
PASS
1       PASS
Expectation: <x-pizza> has a shadow root .

2       PASS
Expectation: <x-pizza> defaults to a blank pizza .

3       PASS
Expectation: <x-pizza> adding toppings updates the pizza state with new toppings .

4       PASS
Expectation: <x-pizza> angular can double-bind to Polymer properties .

All 4 tests passed
I really ought to get a better handle on testing Angular applications, but the hack that I have been using all along in Patterns in Polymer continues to work.



Day #5

1 comment: