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

A simple application that allows inputting names of candidates and present them in a list after applying some formatting
Both components are simple UI components that do not even require a controller

Testing these components the old (and incomplete…) way

  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:

  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.

Yes we can!

Separating the DOM logic from test selectors

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

Introducing TurnerJS!!!

The library is not named after Tina Turner… but Turner Stadium, Hapoel Be'er-Sheva’s home stadium
  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)

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.

Known Limitations

  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?

--

--

--

Senior web developer at Wix.com

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Carmel Cohen

Carmel Cohen

Senior web developer at Wix.com

More from Medium

Should You Use TDD?

TDD Cycle

Common Behavioral Interview Questions For Software Engineers

Frontend unit testing

SOLID in Software Designing