Integrating and Testing Reusable D3 Components in an AngularJS Environment

Posted by on May 29th, 2014
March 18th, 2015

While there are many articles describing the D3 reusable component pattern, I’ve noticed that there isn’t much information on using or testing these components within an AngularJS setting.

In this post I’m going to briefly cover the D3 reusable component pattern, integrating these components into an Angular app and the advantages to doing so. Then we’ll go into testing these visual components using the Karma test runner.

There are a few advantages to using the D3 reusable component pattern:

  • It provides a clear separation of concerns between application logic and drawing logic, making your architecture easier to understand.
  • It eliminates the need to rewrite similar draw logic in multiple places in your application.
  • It makes your draw logic easy to test.

Let’s define a line chart component following the principles of this pattern.

Then it would be used like:

Which gives us this:

D3 reusable component example

Simple enough right? Of course in the real world you would probably want to add axes and other additional functionality, but this should be enough to start testing against.

While our line chart can be used in an Angular app in this form, it isn’t ideal. It violates the principle of dependency injection by exposing a global object throughout your application and has no way to interact directly with Angular code. Now what if you had common logic between multiple charts and wanted to share this via an Angular service? With a simple modification we can make this possible.

In addition to the lineChart service itself, note that I also defined a “wrapper” service, chartService, to give our chart a namespace for readability purposes. This is, of course, up to personal preference, as we could have just injected the lineChart function directly as well.

This can be used similarly to the non-angular version:

Testing

While the benefits to writing unit tests should be obvious, it is a practice that still seems to be the exception rather than the norm. This great Stack Overflow response does a good job detailing the main benefits of testing.

To summarize the response and add to the key points of why testing is so important:

  • Writing code in such a way that makes it testable also enforces an architecture that is more loosely coupled and reusable.
  • You can catch runtime errors immediately upon changing code.
  • Having a suite of unit tests reduces the number of errors introduced due to refactoring.
  • Your fellow developers can more easily understand the purpose of a segment of code by reading the expected functionality from its supporting test.

The gold standard for unit testing angular code is Karma. Karma was created and is maintained by the core Angular team and provides many utilities that make it easy to test angular apps. We will be using the Jasmine syntax with our Karma tests.

Testing our D3 Component

Now that we have our reusable d3 component integrated into Angular, let’s discuss some of the advantages that this brings. We can now move any shared or non drawing specific logic into separate services.

Take our scale setup logic for instance, the getYMax and getXDomain functions that are currently internal to our line chart:

We can now inject this service back into our Angularized lineChart (and any other similarly structured chart!) component and use this in place of our internal getXDomain and getYMax functions:

We are now able to write tests specific to this logic, and alter our drawing logic without worrying about interfering with our scale setup logic.

Now that we have our drawing logic completely isolated, how do we automate the testing of something that is visual? While this does limit us to a certain extent, the fact that Karma executes our tests in the standard browsers with access to the DOM means that we can actually test against the DOM elements. Normally your unit tests should not be aware of implementation details of the tested code, but for testing visual components we have to break this convention slightly. Because our chart assigns different elements (such as the line container, axis container etc) classes, we can select these elements both to see if they exist and to get access to the bound data.

This is the code we are going to use to setup our tests:

How about a simple test to ensure that the exposed getters and setters can be used to update the layout?

This unit test is about as standard as you can get, but what about a test covering the generated DOM components?

Since our line chart creates a container element (an svg with the class chartContainer), which contains our line (a path with the class line), we can test against these elements to see if they were added successfully.

If our getter/setter test passes, we can be confident that updates to our chart layout settings will persist, however this doesn’t ensure that future charts will actually use these updated values.

Now we can be confident that our chart will redraw correctly based on any updates to our configuration. Of course we are just checking that the width is being updated correctly. You should add any additional properties here that you want to test.

We know that our chart will update in response to configuration changes, but more importantly, we want to be sure that it will actually draw with the data being passed.

Here we are testing that the data join is working properly by checking that the input data and bound chart data references are the same. We are also testing that the ‘d’ attribute is not the same as the previous drawing. This lets us know that the drawing did in fact change in response to the new data. This is generally good enough, but, if you really wanted to, you could use test data where you know what the ‘d’ string output should be and test against that.

Since we want our line chart layout to be flexible, we test that we can create more than one chart layout, and render charts unique to these different layouts. We want to make sure that each instance of our chart is completely isolated from the others, and that there are no unintended dependencies between the two (like if our selectors were not specific enough and changing one chart altered the other).

We also would like to test that we can draw more than one line at a time:

If we can draw multiple lines, we’d also like to be sure that we can redraw multiple lines correctly.

Just like the single line test, we are checking against the ‘d’ attribute to see if it has been changed in response to the new data. Again, we could test against a known ‘d’ output if we needed our test to be tighter.

There are more tests included in the code samples on Github, so feel free to check them out. Different types of charts will require different types of tests, but these tests cover functionality that is core to almost any D3 chart. Now you can feel confident when refactoring or adding functionality to your chart. Shoot me any questions or comments below!

Processing...