I am officially a Page Objects convert—especially when it comes to Polymer testing. After a bit of worry, I have Page Objects working in Dart even better that in JavaScript. This begs the question, can I apply some of the lessons learned from Dart back into JavaScript testing?
To answer that question, I am going to first make a slight change to my current Polymer JavaScript test setup. I have been using the Karma test runner, sticking with the default Jasmine test framework. Instead of the default Jasmine framework, I am going to switch to Jasmine 2.0, which (I think) has better support for asynchronous testing.
I start by adding the 2.0 Jasmine dependency to my
package.json
(the 2.0 dependency is karma-jasmine
0.2.0):{ "name": "model_example", "devDependencies": { "grunt": "~0.4.1", "grunt-contrib-watch": "~0.5.3", "karma-jasmine": "~0.2.0", "karma-chrome-launcher": ">0.0" } }After a quick
npm install
, I am ready to go.First up, I need to fix my existing tests because
waits()
, waitsFor()
and run()
are gone in Jasmine 2.0:Chrome 33.0.1750 (Linux) <x-pizza> adding a whole topping updates the pizza state accordingly FAILED ReferenceError: waitsFor is not defined at Object.<anonymous> (/home/chris/repos/polymer-book/play/model/js/test/PolymerSetup.js:14:3) ReferenceError: waitsFor is not defined at Object.<anonymous> (/home/chris/repos/polymer-book/play/model/js/test/XPizzaSpec.js:41:5) TypeError: Cannot read property 'wholeToppings' of undefined at Object.XPizzaComponent.addWholeTopping (/home/chris/repos/polymer-book/play/model/js/test/XPizzaSpec.js:11:29) at Object.<anonymous> (/home/chris/repos/polymer-book/play/model/js/test/XPizzaSpec.js:67:14)That is fairly straight-forward. I need to replace
waitsFor()
with some equivalent that invokes Jasmine's done()
callback. In this case, it is a nice improvement as this:// Delay Jasmine specs until WebComponentsReady beforeEach(function(){ waitsFor(function(){ if (typeof(CustomElements) == 'undefined') return false; return CustomElements.ready; }); });Becomes simply:
beforeEach(function(done) { window.addEventListener('polymer-ready', done); });Learning a lesson from my Dart experience, I replace some ugly
waitsFor()
code with an async()
call:describe('<x-pizza>', function(){ var container, xPizza; beforeEach(function(done){ container = document.createElement("div"); var el = document.createElement("x-pizza"); container.appendChild(el); document.body.appendChild(container); xPizza = new XPizzaComponent(el); xPizza.el.async(done); // waitsFor( // function() { return xPizza.currentPizzaStateDisplay() != ''; }, // "element upgraded", // 5000 // ); }); // Tests here... });Just as with the Dart code, I realize that something is a little off here—I should not be directly accessing the
el
property of the XPizzaComponent
Page Object. All interaction should go through the Page Object itself. The question is, how do I accomplish this without my beloved Futures?Well, it ain't pretty, but enter callback hell:
describe('As with the Dart code, the', function(){ var container, xPizza; beforeEach(function(done){ container = document.createElement("div"); var el = document.createElement("x-pizza"); container.appendChild(el); document.body.appendChild(container); xPizza = new XPizzaComponent(el); xPizza.flush(done); }); // Tests go here... });
flush()
method on the Page Object can invoke async()
to get the Polymer platform to flush changes to observables, redraw things and then invoke the callback:XPizzaComponent.prototype = { // ... flush: function(cb) { if (cb === undefined) cb = function(){}; this.el.async(cb); } };Furthermore, I can invoke supplied callbacks via
async()
when supplied to Page Object interaction methods:XPizzaComponent.prototype = { // ... addWholeTopping: function(v, cb) { // Choose options from select lists, click buttons, etc... return this.flush(cb); }, flush: function(cb) { if (cb === undefined) cb = function(){}; this.el.async(cb); } };So tests that use these page interaction methods can then be simplified to:
describe('adding a whole topping', function(){
beforeEach(function(done){
xPizza.addWholeTopping('green peppers', done);
});
it('updates the pizza state accordingly', function(){
var with_toppings = JSON.stringify({
firstHalfToppings: [],
secondHalfToppings: [],
wholeToppings: ['green peppers']
});
expect(xPizza.currentPizzaStateDisplay()).
toEqual(with_toppings);
});
});
To be sure, this is not as nice as the last night's Dart and Future based Page Object solution. Still, it is vastly superior (faster, more robust) than my previous take. Until Jasmine or some other testing framework supports promises, this will likely have to suffice. And for non-Dart code, it is not too bad.Day #15
Try out thus testing framework with Promise integration :)
ReplyDeletehttp://theintern.io/
Check out this feature comparison ;) Jasmine is sooo 2011
Deletehttp://theintern.io/#compare