Testing angular components — you don’t know what you’re missing

AngularJS components (Angular 1.5+) and directives are great. A component is used in order to encapsulate a UI element as well as its interactions with the user. A component is clear, reusable and can be tested as a unit without having to use any Selenium implementation (such as Protractor).

When looking for way to test components you will probably encounter two basic approaches widely used. The first is to test the controller without the view (for example in Angular component documentation). The second uses $compile service provided by AngularJS in order to render a component and access it via DOM selectors and triggers (click, change, etc.).

What if I told you that there is a third and much better way? That there is a much better and simpler way to do that. That a simple utility can completely change your approach towards component testing.

Let me demonstrate this new approach using an example.
Assume a simple components tree that represents a names list:

A simple application that allows inputting names of candidates and present them in a list after applying some formatting

The template of the main component will probably look something like:

As you may notice, it uses two sub-components — name list and name input, data flows into them as attributes (names) and data is received back as events (onNameAdded).

Let’s look into the name list and name formatter component implementation:

Both components are simple UI components that do not even require a controller

Testing these components the old (and incomplete…) way

UT is your first pick for any Javascript related coding, and the trivial thing to do is to test the component via its controller (which was not even defined until this point).
This requires to register the controller as an angular controller and to expose a method to format the name:

Once controller is extracted, we can unit test it:

There is no need to continue beyond this test in order to identify several problems:

  1. We changed the code in order to be able to test the controller, unit testing something should not require adjustments in the production code.
    Alternatively, we could have used $componentController which will spare the registration of the controller on a module but the rest is still required, and all the issues are still relevant.
  2. We had to expose a lot of the inner behaviour of the component, the method getFormattedName is not really a public API, why should a test fail if I decide to rename it?
  3. There is no guarantee that the view will really use the method we just tested, if by mistake I will not call it properly by calling {{$ctrl.getName()}} the test will still pass, I can remove the element completely and the test is still green.

View unit testing the component to solve the problems above:

AngularJS provides great utilities and services which allow testing the UI on UT level (AKA View Unit Test), by following the best practices we can rewrite the tests as follows:

And the names list (partial list of tests):

These two components are sufficient to notice a list of issues with the way we test them:

  1. The boilerplate is repeated on each test — I might reduce it a bit by using the beforeEach callback, but it means that I can only render the component the same way again in each test, without being able to test it with different attributes (e.g. to test the behaviour when the name list is empty)
  2. If a component is part of a library, anyone uses that library will have to mock the logic in order to be able to test components that use it.
  3. The tests are coupled with the framework in use, there is a mix of values, selectors, calls to $digest, etc. One must have a deep understanding of the framework in order to be able to understand what the purpose of the test/application is.

So we need some kind of an abstraction… so maybe instead of trying to reinvent the wheel, we can figure out how the same problem was solved in a different area — Hardware designers and manufacturers faced a similar problem. Each hardware component is using a completely different chipset, has different capabilities and requires a different set of commands.
An inkjet printer for example requires a set of commands in the form of what cartridge (color) to use and the position of the nozzle (vertical) and page (horizontal).
If the operating system will interact with the printer using these low level commands, it would require a great understanding of the way the printer works, that it has a nozzle, rather than a drum and a laser beam (in the case of a laser printer), it would have to know how many cartridges are used, and how to mix their colors in order to reach the desired color.
Every change to the way the printer works, every upgrade of a component in the printer (or replacement of the entire printer) would require a patch to the operating system.

I am sure you know that this is not really how the interaction between your Operating System and any hardware component works. It was solved by creating a device driver:

In computing, a device driver (commonly referred to simply as a driver) is a computer program that operates or controls a particular type of device that is attached to a computer. A driver provides a software interface to hardware devices, enabling operating systems and other computer programs to access hardware functions without needing to know precise details of the hardware being used. (Wikipedia)

Even our interaction with the web page is done using some kind of a driver (AKA your browser) which maps the clicks, key presses and mouse movements to events and triggers. It also transforms text (HTML and CSS) to a GUI.
Can’t we use the same approach to test a UI component?

Yes we can!

Most articles related to component testing (if not all), are describing the use of Stubs and Drivers. For some reason, when testing our AngularJS components (Or React/Backbone/you-name-it-js) we tend to use stubs (such as jasmine/mocha spies) to isolate the component from services and other components, but not a driver to isolate the component from the way the browser (and user) interacts with our component.
A driver for a UI component will provide a set of user readable methods, isolating how the component is written and rendered, from what we wish to test.
So let’s define a driver to interact with our components and use them in the tests we created above:

The tests are now a lot more readable, and apart from the beforeEach callback, there is nothing that relates to AngularJS, I can refactor the entire application to use a different framework and the tests are still valid, anyone reading the tests understand what the test is aimed to test, what the given scenario is, and what the expected results are. If there is a bug in the way the getFormattedNames method is implemented, I only have to fix it once in the driver rather than in each test that requires interactions with the names.

Separating the DOM logic from test selectors

The driver for name list has to know what the name of the name formatter is (it queries the name of the element). This can be resolved by defining data-* attributes in the view, in order to define selectors which are aimed to access a specific element without having to know what CSS classes it use. 
I tend to use data-hook attribute for this purpose.
In order to use it in our example, we will apply slight changes to the name list view:

And driver:

I am mapping the DOM elements’ text into an array of strings

But this is still not perfect…

  1. There is a coupling between the testing of the name list and the name formatter, changing the logic of the name formatter will fail the second test of the name list.
  2. There is no reuse between the tests. In order to test the name list component, there is a need to access the elements defined in the nested components (list of name formatter components) which requires redefinition of the same selectors defined in NameListComponentDriver in order to access the formatted name, changing the view of name formatter will require adjustments in two drivers.
  3. There seems to be some code that repeats between the drivers — the contractors are identical and the render method is almost identical

Wouldn’t it be great if we can solve all the issues listed above?
To have some kind of a testing utility that will remove all this hassle?
A small utility that will allow us to have drivers that only deals with the real logic of the component with zero boilerplating?
What if I told you that there is already such a utility?

Introducing TurnerJS!!!

The library is not named after Tina Turner… but Turner Stadium, Hapoel Be'er-Sheva’s home stadium

A small utility that we built here at Wix.com. It allows creating a driver (in TypeScript and/or ECMAScript 6 or even plain ECMAScript5) which simplifies the boilerplate hassle and allows creating simple accessors and setters in order to interact with your component in tests.
Drivers can be reused by other drivers since TurnerJS supports nesting of drivers — a driver can create a sub driver or even a list of child drivers (e.g. when testing ng-repeat) and use them in order to interact with sub components.

With TurnerJS, the name formatter driver is shorter and cleaner:

Introduced here are 3 of the APIs provided by the TurnerJS library:

  1. The Driver extends the base component driver included in TurnerJS (TurnerComponentDriver)
  2. renderFromTemplate — This method takes care of the rendering of the template, creation of the new scope and applying the changes (running digest). It accepts two parameters: the template to render, and an object that describes what parameters are defined on the template. For example since the name formatter is expecting a name attribute, we have to pass it to that object in order to use it in the template.
  3. findByDataHook — a built in selector to query an element by the data-hook attribute (or elements by using findAllByDataHook)

Since TurnerJS based drivers are reuseable, while testing the name list, you can define a list of name formatter drivers in order to test names related logic instead of repeating logic.

You may notice that we’ve used another TurnerJS method — defineChildren that allows defining a set of nested component drivers, in such a way that each will interact with a single instance of the nested component (defineChild can be used when there is exactly one instance of the nested component).

As reflected in the second test, the name list can check whether a name it defined exists within the nested formatted names, but it does not have to know how the name is formatted.

What’s more amazing is that when the list of names is changed, there is no need to define the name drivers again, the original array of name drivers was modified in order to reflect the updated value — TurnerJS will update the array itself for you every time the dom was modified (hooray!)
This allows creating tests that check the dynamic behavior of the list:

The drivers share the same hierarchies as the application, so each driver has to deal with nothing more than the logic of the component it interacts with, any sub component is interacted with using its driver. The driver for the main app is not even aware of NameFormatterComponentDriver but it might be interested in getting all the formatted names in order to check that a newly added name is found in the list, so it only has to use getFormattedNames exposed in its child driver, and TurnerJS will automatically create the link between the NameListComponentDriver and its nested driver (or any other defined child) in order to be able to retrieve the formatted name of each name in the list.

And once the name-input driver and app driver are defined, testing the components is fast, precise and clear which leads to better coverage and direct feedback on every change.
Let’s define the drivers for the remaining components:

Now let’s use them in order to create a couple of tests for the name app

Looks pretty clear and simple right? But just look at what the test is actually validating. It is testing that the integration between the name app and name list is working, it is testing that the name app can render an input, and use it in order to update the list.

I will not include all the tests in this article, but you can see them in the sample application included in utility’s open-sourced repo on github (It is written in TypeScript but can easily be read as ES6 Javascript).
There you will also find an example for ES5 (no classes) based tests.
BTW, I’ve used TurnerJS to test older AngularJS 1.3 directives so it is not only aimed for components written in AngularJS 1.5+.

So what did we gain?

  1. TurnerJS supports hierarchies, so the drivers can be reused in parent components.
  2. The use of drivers simplifies the boilerplating of UI based unit testing, no further need to inject $complie/$rootScope services
  3. The tests are far more readable, it is clear what each test is intended to achieve. If someone is interested in the logic of how to interact with the UI they can refer to the driver itself, so there is a separation of both concern and control.

Also, Imagine that each 3rd party library would provide an API for a testing phase in order to interact with the component it creates,
For example imagine that Angular Bootstrap date picker would have included a DatePickerDriver with a selectDate(date) method that can be used when testing components with a datepicker, that would save the use of tons of complex mocks and stubs.

Such approach allows creating smaller UI components, test each with a reuseable driver, and build (and test) larger and larger components, each having a specific role. And later, by using their drivers, create few tests that only checks the integration between the components, without having to deal with the entire application for every scenario (e.g. We don’t need to test empty input validation since that will be tested in the name input tests).
I’ve created complete pages, built with several components, without even having to open a browser (and use the browser later only to complete the styles and sharpen the animations).

Known Limitations

TurnerJS is used for view unit testing, and as such it has the same limitations as other component/directive testing suggestions/frameworks/tutorials:

  1. CSS definitions — you can load CSS files in testing, but it is possible that some of the definitions will not apply as they rely on parent elements that are not included in the template of the component. For that reason you cannot test that ng-show/ng-hide will really make the elements invisible (ng-if works just fine)
  2. Overlapping elements — related to the previous item, but it is important to note that if element A is hiding element B, B is still clickable when accessed by selectors, so Selenium based frameworks (such as Protractor) are still needed to test that an element is really clickable
  3. Navigation — view unit is aimed to test one component at a time, so there is no way to test navigation between one page component to another, one can still test that a route has changed, but that is not a complete test.

And now that you are all thrilled, what’s next?

I can go on and on about Turner (both the JS library and the stadium), but I hope that I managed to pique your curiosity enough so you’ll go ahead and give it a try. Just head to TurnerJS site and give it a try.
I can honestly say that it has completely changed the way some of the teams at Wix.com are testing their UI and even the way the production code is written (more component oriented), so I can only hope it will do the same for you.