Deprecated. See https://www.polymer-project.org/articles/unit-testing-elements.html for the latest version.
Note: this guide is a work-in-progress and will be added to the Polymer docs when it's ready. We have updated <seed-element>
to include unit tests and this guide has been moved to Google docs. Expect a version on the Polymer site before the end of September.
After spending days working on your <super-awesome>
Polymer element, you’re finally ready to share it with the rest of the world. You add the code for using it to your demo, iterate on it over time and come back to it one day when..uh oh. The demo broke because something has gone horribly wrong. Suddenly, <super-awesome>
isn’t starting to look so great. Now you’re stuck trying to backtrack through your commit log to figure out how you broke the code. You’re not going to have a fun time.
If you’ve been working on the front-end for a while, even if you haven’t really played with Polymer elements before, this scenario probably sounds familiar. What makes this really hard is that it took so long to discover there a problem in your code. Something that bit you is probably going to bite any developers using or extending your element. Finding the issue might seem like trying to find a needle in a haystack, but it’s got to be done.
In this short write-up, we’re going to cover some tips on testing Polymer elements to help you catch blocking bugs before they get into your otherwise flawless codebase. Okay, maybe not so flawless, but tests are important and they’ll help. Onwards!
##What do we use?
The Polymer team currently uses some familiar tools for unit testing elements - namely, Mocha and Chai. Using Mocha as our testing framework allows us to write tests that support development and integration without the need for workarounds.
Mocha (as you probably know) was designed to be flexible and needs a few extra pieces in order to make it complete. Perhaps the most important extra piece we use is a JavaScript assertion library called Chai. Chai supports a lot of the assertion styles you might want to use (expect, assert, should). The real difference in these styles is their readability and you’ll likely pick a style that fits in with your or your teams own preferences.
We can quickly compare the differences in assertion styles using an existing element as an example. Let’s take <core-selection>
, which is used for making a list of items selectable.
In our HTML, let’s assume we’ve added core-selection as follows:
<core-selection></core-selection>
And then somewhere else in our application, we’re listened out for events being emitted from this element once an item has been selected in it. The event in question is core-select
. A simple assertion test using an assert-style test for this could be written as follows:
var selector = document.querySelector(‘core-selection’);
selector.addEventListener(‘core-select’, function(event){
assert.equal(event.detail.item, ‘(item)’);
done();
});
selector.select(‘(item)’);
Similarly, an alternative assertion style using expect
could look as follows:
var selector = document.querySelector(‘core-selection’);
selector.addEventListener(‘core-select’, function(event){
expect(event.detail.item).to.equal(‘(item)’);
done();
});
selector.select(‘(item)’);
A should
style assertion, which some developers find closer to natural language could also be written in a similar vein:
var selector = document.querySelector(‘core-selection’);
selector.addEventListener(‘core-select’, function(event){
event.detail.item.should.equal(‘(item)’);
done();
});
selector.select(‘(item)’);
Chai supports all of the above assertion styles but we’re going to use the first (assert style) for the sake of simplicity.
Note: You may find it useful to git clone git://github.com/Polymer/core-tests
on your local machine to take a look at how we currently approach testing. In addition, the tests directory actually already contains Mocha, Chai and all of the helpers we'll be using in this guide, in case you want to quickly reuse them in an existing project.
The Polymer team’s basic infrastructure for testing elements is off-the-shelf Mocha. We haven’t done anything super sophisticated other than using Mocha and Chai with Karma - a popular test runner.
Our only real inflection for testing is a way to run entire HTML pages (e.g pages containing elements) as individual tests in a suite (and Karma supports suites structured in this way).
For example, the tests for core-selector
and core-collapse
(a collapsable widget) are defined in individual HTML pages:
- core-selector.html (basic tests)
- core-selector-multi.html (tests for multiple selection)
- core-collapse.html
All of the things we test are generally Custom Elements which generally have DOM, templates and use Shadow DOM. You don’t have to use our conventions for testing at all if you find they don’t match your tastes. Our approach allows us to do real tests that include HTML easily, but sacrifices on speed for that. If you don’t care about running lots of tests constantly, then our approach works quite well.
A Polymer test for an element like core-selector
starts a little like this:
We begin with a new HTML file which we can call core-selector-tests.html
. This will contain our tests, but before we begin adding those, we'll need to include a few dependencies:
<!— Polymer’s Web Component Polyfills —>
<script src="../platform/platform.js"></script>
<!-- Mocha and Chai -->
<link rel="stylesheet" href="../mocha/mocha.css" />
<script src=“../mocha/mocha.js”>
<script src=“../chai/chai.js”>
<!-- A helper -->
<script src="tools/htmltest.js"></script>
<!— And of course the element we want to test —>
<link rel="import" href="../../core-selector/core-selector.html">
<body>
...
There's nothing too crazy going on here. htmltest just references a little helper we use for styling output and working with the test suite. Mocha and Chai are of course references to those dependencies and then you have your core-selector
element.
You can install Mocha and Chai in your Polymer project using Bower as follows:
bower install mocha chai --save
So back to HTML - let’s define an instance of our element which includes some actual items:
<core-selector id=“selector1”>
<div id=“item1”>Item 1</div>
<div id=“item2”>Item 2</div>
<div id=“item3”>Item 3</div>
</core-selector>
Moving on to JavaScript, we can define our tests as asoon as we’re sure Polymer is fully ready. We can do this by specifying them inside the polymer-ready
event:
document.addEventListener(‘polymer-ready’, function(){
…
});
Inside of here we’re going to define a little alias for chai.assert
:
var assert = chai.assert;
Next, we'll query the DOM for the element we just defined (selector1
):
var s = document.querySelector(‘#selector1’);
We can now begin testing our element. Let’s test that nothing is by default selected (i.e that our current selection is null).
assert.equal(s.selected, null);
Great. How about testing if an attribute is the default value we expect it to be? core-selector
supports a multi
attribute in case you want to support multiple items being selectable. Let’s test that.
assert.isFalse(s.multi);
As core-selector
has a property items
representing the current list of items defined as children, we can also test to make sure it understands that we have 3 items at the moment.
assert.equal(s.items.length, 3);
core-selector
by default uses a specific CSS class to highlight when an item is selected. It’s called core-selected
(big surprise!). A user can override this class by setting the custom selectedClass
attribute on this element. Let’s test to make sure the right class (default) is set.
assert.equal(s.selectedClass, ‘core-selected’);
What about testing events? Well, let’s setup a little counter to track core-select
events.
var selectEventCounter = 0;
Now let’s hook into listening out for the core-select
event, incrementing the counter if we’ve detected an item definitely been selected. If this is the case two properties - s.selectedItem
and e.detail.item
(returned by the event) should be the same.
s.addEventListener(‘core-select’, function(e){
if (e.detail.isSelected){
selectEventCounter++;
assert.equal(e.detail.item, s.selectedItem);
}
});
Great. Now to set an item (say, #item2
) as selected we can simply do:
s.selected = ‘item2’;
In Polymer’s unit tests, just to ensure that all of our bindings are correctly getting updated when we dynamically change values in this way, we can call Platform.flush()
:
Platform.flush();
Finally, to check the value has been set and the selected classes and item values as as expected, we can use a simple timeout as follows:
setTimeout(function(){
// check core-select event
assert.equal(selectedEventCounter, 1);
// check selected class
assert.isTrue(s.children[1].classList.contains(‘my-selected’));
// check selectedItem
assert.equal(s.selectedItem, s.children[1]);
}, 50);
There we have some simple assertion tests against attributes and events for a Polymer element. Fore a more complete reference to how we’ve gone about unit testing some of our elements, including core-selector
, take a look at core-tests - a repository containing a sampling of unit tests for some of our visual and non-visual elements.
One step missing from our workflow so far is how we actually go about running our HTML tests. Let's create a new test runner file for doing that. Create a new file called tests.html
. Inside this file we're going to once again reference Mocha and Chai, but we're also going to include a HTML test helper for Mocha.
<!-- Mocha and Chai -->
<link rel="stylesheet" href="../mocha/mocha.css" />
<script src=“../mocha/mocha.js”>
<script src=“../chai/chai.js”>
<!-- Mocha HTML Test helper -->
<script src="tools/mocha-htmltest.js"></script>
Now that we have these pieces in place, we can setup Mocha and finally run our HTML tests for the core-selector
element. In a new script
block, add the following:
mocha.setup({ui: 'tdd', slow: 1000, timeout: 5000, htmlbase: ''});
Next, define a new suite of HTML tests for core-selector
using the htmlSuite()
method and actually run them using htmlTest()
. For the sake of demonstration, let's assume we have more than one set of HTML tests for our element, testing different possible configurations:
htmlSuite('core-selector', function() {
htmlTest('tests/core-selector-basic.html');
htmlTest('tests/core-selector-activate-event.html');
htmlTest('tests/core-selector-multi.html');
});
Great. Finally, we want Mocha to run these tests when we launch the page, so we'll tell Mocha to run next.
Mocha.run();
That's it!.
We’ve taken a brief look at unit testing Polymer elements. For the most part, testing Web Components isn’t vastly different to unit testing the same JavaScript components you build everyday. You’re still working with events, objects and an API. The beauty of sticking with Mocha and Chai here is that tests can execute equally well in both the browser as part of a build process or as part of continuous integration.
As always, we’re always happy to help with input if you get stuck over on StackOverflow.
I've gotten this working in the browser but I'm having trouble getting karma to collect test results from test_runner.html. More details would be appreciated.