Skip to content

Instantly share code, notes, and snippets.

@addyosmani
Last active July 27, 2020 05:17
Show Gist options
  • Star 24 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save addyosmani/b318ca7618eed38c05e1 to your computer and use it in GitHub Desktop.
Save addyosmani/b318ca7618eed38c05e1 to your computer and use it in GitHub Desktop.
Unit Testing Polymer Elements

Deprecated. See https://www.polymer-project.org/articles/unit-testing-elements.html for the latest version.

Unit Testing Polymer Elements

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.

Polymer conventions

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.

Testing Attributes & Events

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.

Running tests

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!.

Conclusion

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.

@addyosmani
Copy link
Author

Just to note: I've since added support for unit testing Polymer elements to https://github.com/PolymerLabs/seed-element

@ulybu
Copy link

ulybu commented Jul 8, 2014

Would a paragraph on the karma setup be out of scope ?

@cayblood
Copy link

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.

@enguyen
Copy link

enguyen commented Nov 20, 2014

Are there any resources for understanding how to mock out dependencies? A typical example is needing to mock out a component that's imported by my component under test.

@addyosmani
Copy link
Author

See https://www.polymer-project.org/articles/unit-testing-elements.html for the new, proper version of the guide. It covers using our new tool for testing (web-component-tester).

@martindale
Copy link

@addyosmani any plans to update this to 1.0? Would love seeing an updated version on the site, too.

@RoopGuron
Copy link

Does WCT generate and persist reports on test results, to be used for build sanity in Continuos Integration set up
Is there a hook in WCT to pass to mocha to save test results to a file? If yes please share information.
Thanks

@leegee
Copy link

leegee commented Jun 16, 2016

Integration with Karma for CI would be very helpful.

@montrealist
Copy link

Links to unit-testing-elements.html above are 404. Here's a more recent doc that I managed to find:

https://www.polymer-project.org/1.0/docs/tools/tests

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment