For some time now, I have primarily been using logic-less templating solutions as they allow for a greater separation of concerns in comparison to many of their logic-based counterparts. By design, the decoupling of logic-less templates imparts greater overall maintainability in that templates become considerably less complex, and therefore, considerably easier to maintain and test.
Handlebars, my preferred logic-less templating engine, simplifies testing even further via it’s elegant Helper API. While Handlebars may not be the fastest templating solution available, I have found it to be the most testable, reusable and thus, maintainable.
Custom Handlebars Helpers
Since Handlebars is a logic-less templating engine, the interpolation of values which require logical operations and/or computed values is facilitated via Helpers. This design is quite nice in that template logic can be tested in isolation from the context in which it is used; i.e. the templates themselves. In addition to the common built-in Block Helpers, custom Helpers can easily be registering in order to encapsulate the logic used by your templates.
Registering Custom Helpers
Registering Custom Helpers is as simple as invoking Handlebars.registerHelper
; passing the string name of the helper which is to be registered, followed by a callback which defines the actual helpers implementation.
Consider the following custom Helper example, which, given a string of text, replaces plain-text URLs with anchor tags:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | ( function() { // define markup enhancement regex var protocol = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim, scheme = /(^|[^\/])(www\.[\S]+(\b|$))/gim; /* * Registers a Helper method with Handlebars which, given a string of * plain text or existing markup, provides enhancements of plain text * URLs, converting them to their respective anchor tag equivalents. */ Handlebars.registerHelper( 'enhance', function( text ) { text = text.replace( protocol, '<a href="$1" target="_blank">$1</a>'); text = text.replace( scheme, '$1<a href="http://$2" target="_blank">$2</a>' ); return new Handlebars.SafeString( text ); }); }()); |
(Gist)
As can be seen in the above example, custom Handlebars Helpers are registered outside the context of the templates in which they are used. This allows us to test our custom Helpers quite easily.
Testing Custom Helpers
Generally, I prefer to abstract testing custom Helpers specifically, and test the actual templates which use the Helpers independently from the Helpers. This allows for greater portability as it promotes reuse in that common custom Helpers (and their associated tests) can then be used across multiple projects, regardless of the templates which use them. While one can test Handlebars implementation code with any testing framework, in this example I will be using Jasmine.
Essentially, testing Custom Helpers is much the same as testing any other method. The only point to be noted is that we first need to reference the helper from the Handlebars.helpers namespace. Ideally this could be avoided as, should the namespace happen to change, so, too, will our tests need to change. That being said, the probability of such a change is unlikely.
Using the above example, in a Jasmine spec, the enhance
helper can be referenced as follows:
| describe( 'Custom Handlebars Helpers', function() { // define a reference to the underlying helpers namespace. // we can then access our helpers as methods and test them // as needed var helpers = Handlebars.helpers; }); |
Then we can test that the helper was registered:
| describe( 'Custom Handlebars Helpers', function() { var helpers = Handlebars.helpers; describe( 'The "enhance" markup helper', function() { it ('should be registered', function() { expect( helpers.enhance ).toBeDefined(); }); }) }); |
We can then test any expectation. For example, the enhance
helper should return a Handlebars.SafeString. We can test this as follows:
| describe( 'Custom Handlebars Helpers', function() { // define a reference to the underlying helpers object. var helpers = Handlebars.helpers; describe( 'The "enhance" markup helper', function() { it ( 'should be registered', function() { expect( helpers.enhance ).toBeDefined(); }); it ( 'should return a Handlebars.SafeString', function() { var isSafeString = helpers.enhance("") instanceof Handlebars.SafeString expect( isSafeString).toBeTruthy(); }); }) }); |
The enhance
helper is expected to replace plain-text URLs with anchor tags. Before testing this, though, we should first test that it preserves existing markup. In order to test this use-case, we first need to access the return value from our custom Helper, we can do this by referencing the string
property of the Handlebars.SafeString returned by our Helper:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | describe( 'Custom Handlebars Helpers', function() { // define a reference to the underlying helpers object. var helpers = Handlebars.helpers; describe( 'The "enhance" markup helper', function() { it ( 'should be registered', function() { expect( helpers.enhance ).toBeDefined(); }); it ( 'should return a Handlebars.SafeString', function() { var isSafeString = helpers.enhance("") instanceof Handlebars.SafeString expect( isSafeString).toBeTruthy(); }); it ( 'should preserve existing markup', function() { var expected = '<strong>Some unescaped markup</strong> and a <a href="#">link</a>'; // We access the SafeString.string property to test // our actual return value... var actual = helpers.enhance( expected ).string; expect( actual ).toEqual( expected ); }); }) }); |
Finally, we test that our enhance
Helper replaces URLs with anchor tags using the above techniques:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | describe( 'Custom Handlebars Helpers', function() { // define a reference to the underlying helpers object. var helpers = Handlebars.helpers; describe( 'The "enhance" markup helper', function(){ it ( 'should be registered', function() { expect( helpers.enhance ).toBeDefined(); }); it ( 'should return a Handlebars.SafeString', function() { var isSafeString = helpers.enhance("") instanceof Handlebars.SafeString expect( isSafeString).toBeTruthy(); }); it ( 'should preserve existing markup', function() { var expected = '<strong>Some unescaped markup</strong> and a <a href="#">link</a>'; // We access the SafeString.string property to test // our actual return value... var actual = helpers.enhance( expected ).string; expect( actual ).toEqual( expected ); }); it ( 'should replace URLs with anchor tags', function() { var expected = 'Check out <a href="http://handlebarsjs.com" target="_blank">http://handlebarsjs.com</a>' actual = helpers.enhance( 'Check out http://handlebarsjs.com' ); expect( actual.string ).toEqual( expected ); }); }) }); |
(Gist)
We now have a complete test against our custom Helper, and all is green:

Note: The above Spec Runner is using the very nice jasmine.BootstrapReporter
And that’s all there is to it. You can fork the example at handlebars-helpers-jasmine.
This Article was published on Tuesday, March 13th, 2012 at 5:36 PM and is filed under APIs, HTML5, JavaScript, Object Oriented Design.
You can follow Comments to this entry through the RSS 2.0 feed.Both comments and pings are currently closed.
{Sorry, Comments are currently Closed! }