Monday, December 8, 2014

Testing Polymer (Dart) Mixins


I need to improve my continuous integration solutions for Patterns in Polymer. I do not know that I necessarily need to include a chapter on it in the book, but I ought to at least be able to point to some solutions in my research notes. Especially since I have been asked about it multiple times this week alone.

With that in mind, I first need to test my AFormInputMixin class from last night, which serves as mixin or baseclass for other Polymer.dart elements so that they can behave like native HTML form input elements. This turns out to be a little trickier than I had expected, but I'll get to that in a bit.

I start with the usual Polymer.dart test/index.html entry point in this package's pubspec.yaml file:
name: a-form-input
author: "Chris Strom <github@eeecomputes.com>"
description: "Polymer element that mimics a native <form> input."
dependencies:
  polymer: any
dev_dependencies:
  unittest: any
transformers:
- polymer:
    entry_points:
      - test/index.html
The test/index.html page loads a concrete element that uses the mixin, then pulls in the necessary test code:
<!doctype html>
<html>
<head>
  <!-- Load component(s) -->
  <link rel="import" href="x-double.html">

  <!-- The actual tests -->
  <script type="application/dart" src="test.dart"></script>
  <script src="packages/unittest/test_controller.js"></script>
</head>
<body>
</body>
</html>
The test/test.dart file is pretty typical Polymer testing code. It pulls in the necessary libraries, initializes Polymer (there can be only one main() so the test entry point has to start Polymer), creates DOM containers for the test elements and establishes tear downs for the same:
library plain_old_forms_test;

import 'package:unittest/unittest.dart';
import 'dart:html';
import 'dart:async';
import 'package:polymer/polymer.dart';

main() {
  initPolymer();

  var _el, _form, _container;
  group("AFormInputMixin mixed into <x-double>", (){
    setUp((){
      _container = createElement('<div></div>');
      _form = createElement('<form></form>');
      _el = createElement('<x-double></x-double>');

      _form.append(_el);
      _container.append(_form);
      document.body.append(_container);
    });

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

    group('acts like <input> -', (){
      setUp((){
        _el.name = 'my_field_name';
        _el.start = '21';

        var completer = new Completer();
        _el.async(completer.complete);
        return completer.future;
      });

      test('value property is updated when internal state changes', (){
        expect(_el.value, '42'); // Doubled by <x-double>
      });
      // ...
    });

  });
}
Aside from the funkiness of the async() callback in setUp(), the tests themselves are straight-forward. In the test above, I ensure that the <x-double> example element places the doubled start value on the value property. This all works in the browser, so now I need a script that can be used on a continuous integration server.

I start by running some static type analysis with test/run.sh:
#!/bin/bash

# Static type analysis
results=$(dartanalyzer lib/a_form_input.dart 2>&1)
echo "$results"
if [[ "$results" != *"No issues found"* ]]
then
    exit 1
fi
echo "Looks good!"
echo
That works fine, so now it is time for the actual tests. I start those with the standard content_shell:
#!/bin/bash

# Static type analysis....

# Run a set of Dart Unit tests
results=$(content_shell --dump-render-tree test/index.html)
echo -e "$results"

# check to see if DumpRenderTree tests
# fails, since it always returns 0
if [[ "$results" == *"Some tests failed"* ]]
then
    exit 1
fi

if [[ "$results" == *"Exception: "* ]]
then
    exit 1
fi
And here is where I run into problems. All of the tests pass when run from pub serve, but with content_shell, I get:
CONSOLE MESSAGE: warning: polymer.html not found.
CONSOLE WARNING: line 213: Error:

www.polymer-project.org.

CONSOLE WARNING: line 213: FAIL
CONSOLE ERROR: Exception: Bad state: polymer.js must be loaded before polymer.dart, please add <link rel="import" href="packages/polymer/polymer.html"> to your <head> before any Dart scripts. Alternatively you can get a different version of polymer.js by following the instructions at http://www.polymer-project.org

Since I am mixing the solution in this package into another class, I seem to be running afoul of resource loading limitations and the lack of transformers being run on local resources. So I have to borrow my solution for testing ajax resources with a pub server in the test:
#!/bin/bash

# Static type analysis....

pub serve &
pub_pid=$!

# Wait for server to build elements and spin up...
sleep 15

# Run a set of Dart Unit tests
results=$(content_shell --dump-render-tree http://localhost:8080)
echo -e "$results"

kill $pub_pid

# check to see if DumpRenderTree tests fail...
That is less than ideal, but it does the trick:
$ /test/run.sh
Analyzing [lib/a_form_input.dart]...
No issues found
Looks good!

Loading source assets...
Loading polymer transformers...
Serving a-form-input test on http://localhost:8080
Build completed successfully
...
CONSOLE MESSAGE: unittest-suite-wait-for-done
CONSOLE MESSAGE: PASS: AFormInputMixin mixed into <x-double> has a shadowRoot
CONSOLE MESSAGE: PASS: AFormInputMixin mixed into <x-double> acts like <input> - value property is updated when internal state changes
CONSOLE MESSAGE: PASS: AFormInputMixin mixed into <x-double> acts like <input> - value attribute is updated when internal state changes
CONSOLE MESSAGE: PASS: AFormInputMixin mixed into <x-double> acts like <input> - form value attribute is updated when internal state changes
CONSOLE MESSAGE: PASS: AFormInputMixin mixed into <x-double> acts like <input> - containing form includes input with supplied name attribute
CONSOLE MESSAGE: PASS: AFormInputMixin mixed into <x-double> acts like <input> - setting the name property updates the name attribute
CONSOLE MESSAGE:
CONSOLE MESSAGE: All 6 tests passed.
CONSOLE MESSAGE: unittest-suite-success
CONSOLE WARNING: line 213: PASS
It might be nice for this to work without the server, so I may take some time tomorrow to explore that a bit. Regardless, I have a solution for testing Polymer.dart base and mixin classes.


Day #18

4 comments:

  1. > I do not know that I necessarily need to include a chapter on it in the book,

    I would love to have a chapter on this in THE book : for me TDD goes in pair with continuous integration with regression tests. What the point of writing tests if they don't tell you when thing become broken ?

    ReplyDelete
    Replies
    1. I completely agree that there is no point to writing tests unless you run them all the time. The only reason that I would not include it in the book proper is that such a solution would be tied to a specific CI service. The book is really meant for solutions that transcend the Dart and JavaScript implementations. I can do that with testing approaches, but I don't think it's possible with testing services.

      I could be wrong (and I usually am!). I'll play around with it over the next couple of days to be sure...

      Delete
  2. I converted bash script to Dart test app.
    GitHub: https://github.com/DisDis/DartUITest.git

    ReplyDelete
    Replies
    1. Nifty! I normally prefer Bash scripts, but Dart scripts have definite cross-platform advantages. I have never tried running the analyzer as a service like that -- very good to know. Thanks!

      Delete