Monday, April 6, 2015

Replacing Karma with web-component-tester in Polymer Projects


It looks as though I will be replacing Karma with web-component-tester as the testing framework in Patterns in Polymer. I only really need to do the in the two testing chapters, but I'm in for a penny...

To get started, I will switch testing for the very simple <hello-you> Polymer element from chapter 1 of the book, then upgrade to the latest Polymer to see if anything breaks. That seems like a nice little test of things.

I start by simply replacing the karma dependencies in my chapter's package.json with web-component-tester (WCT):
{
  "name": "hello-you",
  "devDependencies": {
    "grunt": "~0.4.5",
    "grunt-contrib-watch": "~0.6.1",
    "grunt-notify": "^0.4.1",
    "web-component-tester": "^2.2.6"
  }
}
I remove the Karma configuration, the test/PolymerSetup.js that was needed in Karma, and the existing node_modules. Then I install the new WCT dependencies:
➜  polymer git:(master) rm -rf karma.conf.js test/PolymerSetup.js node_modules; npm install 
From there, I can convert my tests fairly easily. I had defined the test files in the old karma.conf.js. The web-component-tester expects the test entry point to reside in test/index.html. So I create test/index.html and point it the existing test file:
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <script src="../bower_components/webcomponentsjs/webcomponents.min.js"></script>
  <script src="../../web-component-tester/browser.js"></script>
  <link rel="import" href="../elements/hello-you.html">
</head>
<body>
  <hello-you id="fixture"></hello-you>
  <script src="index.js"></script>
</body>
</html>
I also add a fixture in there for easier access to the Polymer element that I want to test.

The tests themselves nearly work without change. I had been using Jasmine for my tests. The BDD interface supported by Chai is very similar to Jasmine—all I need to change is my assert statements:
describe('<hello-you>', function(){
  var el;

  beforeEach(function(){
    el = document.querySelector('#fixture');
  });
  // ...
  describe('typing', function(){
    beforeEach(function(done){
      var input = el.shadowRoot.querySelector('input');
      var e = document.createEvent('TextEvent');
      e.initTextEvent('textInput', true, true, null, 'Bob');
      input.dispatchEvent(e);
      setTimeout(done, 0); // One event loop for Polymer to process
    });

    it('says a friendly hello', function() {
      var h2 = el.shadowRoot.querySelector('h2');
      assert.equal(h2.textContent, 'Hello Bob');
    });
  });
});
Just like that, I have passing tests:
chrome 41                Beginning tests via http://localhost:59285/polymer/test/index.html?cli_browser_id=0
chrome 41                ✓ index.html » <hello-you> » element content » has a shadow DOM
chrome 41                ✓ index.html » <hello-you> » typing » says a friendly hello
chrome 41                Tests passed
Test run ended with great success

chrome 41 (2/0/0)

Done, without errors.
I have another test that relies on Jasmine spies. WCT supports spies, but Sinon stubs instead of Jasmine spies. In this case, I want to stub the return value of Math.random so that the otherwise random color change is deterministic. Sinon (like Jasmine) makes this easy:
  describe('clicking', function(){
    beforeEach(function(){
      // spyOn(Math, "random").and.returnValue(0.99);
      sinon.stub(Math, "random").returns(0.99);
    });

    it('randomly changes the title\'s color', function(){
      var button = el.shadowRoot.querySelector('input[type=submit]');

      var e = document.createEvent('MouseEvent');
      e.initMouseEvent(
        "click", true, true, window,
        0, 0, 0, 80, 20,
        false, false, false, false, 0, null
      );
      button.dispatchEvent(e);

      var h2 = el.shadowRoot.querySelector('h2');
      assert.equal(h2.style.color, 'green');
    });
  });
With that, I again have three passing tests for my simple element:
chrome 41                Beginning tests via http://localhost:37627/polymer/test/index.html?cli_browser_id=0
chrome 41                ✓ index.html »  » element content » has a shadow DOM
chrome 41                ✓ index.html »  » typing » says a friendly hello
chrome 41                ✓ index.html »  » clicking » randomly changes the title's color
chrome 41                Tests passed
Test run ended with great success

chrome 41 (3/0/0)                     

Done, without errors.
My next step is to upgrade the Polymer library to verify that (hopefully) nothing breaks. For the book, I have left Polymer unconstrained in Bower:
{
  "name": "hello-you",
  // ...
  "dependencies": {
    "polymer": ">0.0"
  }
}
So a simple bower update gets me to Polymer 0.8:
$ bower update
...
polymer#0.8.0 bower_components/polymer
├── core-component-page#0.5.5
└── webcomponentsjs#0.6.0

webcomponentsjs#0.6.0 bower_components/webcomponentsjs
With that, I re-run my tests and find… that two tests are now failing:
chrome 41                Beginning tests via http://localhost:42253/polymer/test/index.html?cli_browser_id=0
chrome 41                ✖ index.html » <hello-you> » element content » has a shadow DOM

  expected null to not equal null
    <unknown> at   Context.<anonymous> at /polymer/test/index.js:10:0

chrome 41                ✖ index.html » <hello-you> » clicking » randomly changes the title's color

  Cannot read property 'querySelector' of null
    <unknown> at   Context.<anonymous> at /polymer/test/index.js:44:0

chrome 41                Tests failed: 2 failed tests
Test run ended in failure: 2 failed tests

chrome 41 (0/0/2)                     
2 failed tests
At first, I suspect that there is a newer version of the web-component-tester that I need for Polymer 0.8. Then I take a closer look a the 0.8 Migration Guide and… wow. I am in some serious trouble with 0.8. Not only has the API completely changed, but many of the core concepts that have been baked in to Polymer for nearly two years have changed. Shady DOM? Ouch.

With not _too_ much effort, I am able to get my very simple element migrated to 0.8 and the tests passing again. But I need to give some very serious consideration to how I plan to handle 0.8 in the book. Given that it is only a preview release at this point, I may push to get the latest version of the book done sooner rather than later then deal with the repercussions of 0.8 after the new API stabilizes. Ugh. And I was having such a nice time with web-component-tester.


Day #21

1 comment:

  1. Thanks! Your click example really helped. I have a button that, when clicked, initiates an ajax call, which seems to fail immediately when I test it in Chrome. The Timing tab says "stalled". Any idea how I fix that in web-component-tester?

    ReplyDelete