Friday, September 19, 2014

Testing Core-Ajax Polymer Elements with Karma


One of the benefits of a test runner like Karma is that it, y'know, runs tests. More accurately, it runs tests on a test web server, which is a detail that I would like to explore testing the internationalization solution in Patterns in Polymer.

As best I can tell, there is not a single solution for i18n with Polymer. Way back when, I played with using the i18next JavaScript package in Polymer and liked it quite a bit. I just do not know if I like it as the solution. Especially since the book is simultaneously written in JavaScript and Dart, I settled on a strategy that allowed swapping implementations.

Tonight, I hope to test the default JavaScript strategy from the book, which is a simple, hand-rolled solution that loads localization files via <core-ajax> tags. Normally if I am testing Ajax in JavaScript, I would want to pull in the Sinon.js testing library to stub out HTTP requests like those that load the l10n files. But, since Karma is running its own web server for testing, I ought to be able to add the l10n files to the list of files being served and actually load the l10n files in my tests.

In fact, when I run the solitary test that currently tests my i18n chapter, I already see 404s when my Polymer element attempts to load the l10n files:
$  karma start --single-run
INFO [karma]: Karma v0.12.23 server started at http://localhost:9876/
INFO [launcher]: Starting browser Chrome
INFO [Chrome 37.0.2062 (Linux)]: Connected on socket DjAiWyWkVjQjEfu7_8nN with id 73763037
Chrome 37.0.2062 (Linux): Executed 1 of 1 SUCCESS (0 secs / 0.078 secs)
WARN [web-server]: 404: /locales/en/translation.json
WARN [web-server]: 404: /locales/fr/translation.json
Chrome 37.0.2062 (Linux): Executed 1 of 1 SUCCESS (0.096 secs / 0.078 secs)
First, I need to include the l10n files in the list of files that Karma serves:
    files: [
      'bower_components/platform/platform.js',
      'test/PolymerSetup.js',
      {pattern: 'elements/**', included: false, served: true},
      {pattern: 'locales/**', included: false, served: true},
      {pattern: 'bower_components/**', included: false, served: true},
      'test/**/*Spec.js'
    ],
These are not code files—just JSON—so I do not want them automatically included and evaluated on the test page. I just want them served on demand. Hence included: false (the contents are not added directly to the test page) and served: true (the web server will serve them when requested).

That is just part of the puzzle. When I fire up the tests, I still get 404s for the l10n files:
WARN [web-server]: 404: /locales/en/translation.json
WARN [web-server]: 404: /locales/fr/translation.json
This is due to Karma serving everything from the /base URL path. If index.html is at the top-level of my project, then I would need to access it as /base/index.html.

Thankfully, Karma has a somewhat obscure proxies setting which allows resources to be reverse proxied:
proxies:  {
      '/locales/': '/base/locales/'
    }
With that, and a log-level of debug, I see that I have my l10n files being served and proxied:
...
DEBUG [proxy]: proxying request - /locales/en/translation.json to localhost:9876
DEBUG [proxy]: proxying request - /locales/fr/translation.json to localhost:9876
With that, I can write my tests that expect different localizatons:
    it('defaults to English', function(){
      expect(el.shadowRoot.textContent).toContain('Hello');
    });
Updating the locale is slightly tricky, but only because I have to wait for Polymer to update the element display. It only takes a single event loop, but the only way to accomplish this in Jasmine is with a setTimeout call to the asynchronous done():
    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');
      });
    });
But, with that, and a similar test for Spanish:
    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');
      });
    });
Now I have much more meaningful tests for the book's i18n solution. This will be especially useful as Polymer evolves. Should my approach suddenly not localize with a new version of the Polymer library, I will know immediately.

I am marginally dissatisfied with the overhead of beforeEach() just to setTimeout() / done(). I do not normally care too much about the readability of tests, but this seems to unnecessarily obscure the intent of the test. I have larger concerns (like producing screencasts for the Extras readers), so I may just live with this. Still, it bothers me so I may have a look at this in the upcoming days.


Day #188

No comments:

Post a Comment