Saturday, September 20, 2014

Indirection Clean-up of Polymer Jasmine Tests


While testing my JavaScript Polymer elements, I do this a lot:
      beforeEach(function(done) {
        el.locale = 'fr';
        setTimeout(done, 0); // One event loop for element to update
      });
No, really, a lot:
  beforeEach(function(done){
    container = document.createElement("div");
    container.innerHTML = '';
    document.body.appendChild(container);
    el = document.querySelector('hello-you');

    setTimeout(done, 0); // One event loop for elements to register in Polymer
  });
In fact, I do it so often that I hard-coded it in the generator in eee-polymer-tests:
    beforeEach(function(done){
      // Maybe do some prep work here...
      setTimeout(done, 0); // One event loop for Polymer to process
    });
The problem that I am dealing with is, as the comments indicate, that Polymer waits until the next event loop before doing its thing. This promotes Polymer code and elements that are nice and responsive, but makes for noisy tests. I wonder if I might clean things up some.

Before starting down this road, I should say that I probably will not change anything. The first thing that I will try is a helper function. I hate helper functions. I hate indirection in tests. If I have to debug a test or trace through a test, I guarantee that I will delete that test (and maybe replace it). Still, I seem to do this setTimeout() / done() dance a lot, so...

I start by borrowing some of the terminology from the very excellent sheduled_test package in Dart. I try to schedule a Polymer operation in my tests with:
function schedulePolymer(fn){
  return function(done) {
    fn();
    setTimeout(done, 0); // One event loop for elements to register in Polymer
  };
}
By returning a function, I can supply this directly to Jasmine's (or Mocha's) beforeEach() function:
  beforeEach(
    schedulePolymer(function(){
      container = document.createElement("div");
      container.innerHTML = '<hello-you></hello-you>';
      document.body.appendChild(container);
      el = document.querySelector('hello-you');
    })
  );
That works. Well, my tests all still pass. But I am not really sure that clears up intent of my tests sufficiently to justify the existence of this helper function. And although the word schedule is nice in that implies a synchronous operation, I do not feel like it works well in this case. Perhaps if there was an entire library dedicated to scheduling JavaScript tests serially, this would make more sense.

Maybe if I write a Polymer-specific beforeEach():
function beforeEachPolymer(fn){
  return beforeEach(function(done) {
    fn();
    setTimeout(done, 0); // One event loop for elements to register in Polymer
  });
}
This lets me rewrite my beforeEach() as:
    beforeEachPolymer(function(){
      container = document.createElement("div");
      container.innerHTML = '';
      document.body.appendChild(container);
      el = document.querySelector('hello-you');
    });
This has the added benefit of not seeming out of place with afterEach() calls that I typically place immediately after beforeEach() calls:
  afterEach(function(){
    document.body.removeChild(container);
  });
This beforeEachPolymer() is not a huge win, but it does help when performing minor changes before checking expectations. Last night's i18n tests included a series of beforeEach() calls that needed to wait after changing the locale:
    describe('it can localize to French', function(){
      beforeEach(function(done) {
        el.locale = 'fr';
        setTimeout(done, 0); // One event loop for element to update
      });

      it('(fr)', function(){
        expect(el.shadowRoot.textContent).toContain('Bonjour');
      });
    });

    describe('it can localize to Spanish', function(){
      beforeEach(function(done) {
        el.locale = 'es';
        setTimeout(done, 0); // One event loop for element to update
      });

      it('(es)', function(){
        expect(el.shadowRoot.textContent).toContain('Hola');
      });
    });
That is rather noisy — especially since the setTimeout() / done() lines are so much wider than the code that actually tests my Polymer element. With beforeEachPolymer(), I now have:
    describe('it can localize to French', function(){
      beforeEachPolymer(function() {
        el.locale = 'fr';
      });

      it('(fr)', function(){
        expect(el.shadowRoot.textContent).toContain('Bonjour');
      });
    });

    describe('it can localize to Spanish', function(){
      beforeEachPolymer(function() {
        el.locale = 'es';
      });

      it('(es)', function(){
        expect(el.shadowRoot.textContent).toContain('Hola');
      });
    });
The intent of which is much clearer. But is it clear enough to justify test indirection? I just do not know.

I do not think that switching to promises would really offer much help—especially since Polymer is not much of a promise-driven library at present. I loathe to pull in yet another testing library—I already need a runner, a library, and an assertion library. Do I really need to pull in yet another library that people may or may not look upon as a standard? I have to admit, the old Jasmine 1 waits() and runs() stuff is looking pretty nice at this point. I do not think that is a viable solution to offer in Patterns in Polymer, but I may start using that in my own Polymer projects.


Day #189

No comments:

Post a Comment