Home » Angular 2 » Unit Testing Angular(2+) with JSDOM

Unit Testing Angular(2+) with JSDOM

Unit Testing Angular(2+) with JSDOM can be problematic unless you know the secret handshake that allows ZoneJS and JSDOM to coexist.

The great thing about Angular is that you can write Unit Tests from the presentation layer all the way down to calls to the server.  But up until now, you either ran those tests in a browser, which doesn’t work well in a CI system, or you used PhantomJS, which tends to be REALLY slow!  But there is a better way, and hopefully, by the time this post goes live, the patches needed to use JSDOM will be available.  If not, I’ll show you the hack that I’ve found works and the pull request I’m hoping will go live.

Unit Testing Angular(2+) with JSDOM
Photo credit: Juanedc via Visual Hunt / CC BY

Why JSDOM?

As I mentioned in the introduction, there are two problems that JSDOM fixes.

To run the Angular unit tests, you need to run them in a browser.  The problem with this is that you would need to have a browser installed on your CI server to run them, if you run them at all.  It can be done, but if you are working in an environment like the one I work in, it isn’t going to be easy.

The second choice is to use PhantomJS.  Unfortunately, PhantomJS, while easy to install, runs slowly.  For all but the most trivial of applications, this isn’t going to work well.

JSDOM, on the other hand, runs fast like a browser, and doesn’t have the problems that running it on a CI system has.  This is because it is a headless browser that never renders.  All it does is produce HTML.  For unit tests, this is all we really care about.  And because it is running inside of Node, it is running as fast as the V8 engine will let it.  Making it theoretically faster than running the tests in Chrome.  I say, “theoretically faster” because I have not tested this and the V8 engines in the most recent browser tends to be a bit ahead of the V8 engine used in the most recent version of Node.

The Problem

Knowing this was possible caused me to give it a try.  What I found was that I routinely crashed with the following error.

Cannot set property onreadystatechange of [object Object] which has only a getter

As I googled this, I found several fixes for JSDOM that would allow this to work, but I also discovered that JSDOM did not consider this an issue they needed to fix.  And rightly so, why should they adapt just so it would work for Angular2?

But, what in the Angular2 code would cause this problem.  By doing a search for onreadystatechange in my node_modules directory, I discovered that ZoneJS was:

  1. Saving off the original definition of onreadystatechange
  2. Overriding the definition with a getter (only)
  3. Setting the definition back to the original

Which all works well if the original onreadystatechange has a definition.  But in the case of JSDOM, it doesn’t.  Then, when they set the definition back, nothing happens and we keep the definition they created.

Solution 1

The file in question is property-descriptor.ts under the lib/browser directory (the js version is in the file zone.js under the dist directory).  Sticking with the TS file… of version 0.8.5, scroll down to line 64 and you’ll see that they retrieve the current definition but never verify that they actually got something back.  But at line 80 they set it back to an empty object if it doesn’t exist.

The easy fix that seems to work for me, is to just change the new definition so that it works if that is the one that is left over after this function completes:

Because we’ve changed the getter from returning a hardcoded value, we also need to set onreadystatechange

The full fix looks like this:

Solution 2

The current pull request adds a bit more code that I’m assuming is needed.  I haven’t tested this, but I’m assuming it is a safer alternative than my hack.

One Additional Gotcha!

Once I had this basic issue solved, I was able to run my suite of test with the exception of one.  It turns out element.innerText doesn’t exist in JSDOM.  There is a technical reason for this that I won’t discuss here other than to say it is, evidently, somehow dependant on the rendering engine, and since JSDOM has no rendering engine (remember, it just produces HTML) it can’t really implement innerText.  So, I had to refactor my test to use innerHTML instead.  Trivial issue.  Just something you need to be aware of.

Setting Up Karma

Now, from here to the end, we are going to assume that this got fixed, or you are using one of the solutions above.  Now, how do we set karma up to use JSDOM instead of Chrome or PhantomJS?

Well, for starters, you’ll need to npm install --save-dev jsdom karma-jsdom-launcher.

Then, you’ll need to make a few changes to your karma.conf.js file.

First, in the plugins array, add require('karma-jsdom-launcher').

Then, at the bottom of the file, change the browsers line to specify 'jsdom' instead of 'Chrome'.

I normally just comment out the Chrome line and put in a line for jsdom so I can use Chrome to debug when I need to.

I’ve always said that Angular mixes the best of AngularJS and React and with this fix, we now have some of the React Unit Testing goodness added into the mix.

 

Other post in Angular 2
Summary
Unit Testing Angular(2+) with JSDOM
Article Name
Unit Testing Angular(2+) with JSDOM
Description
Unit Testing Angular(2+) with JSDOM can be problematic unless you know the secret handshake that allows ZoneJS and JSDOM to coexist.
Author
DMB Consulting, LLC

About Dave Bush

Dave Bush is a Full Stack ASP.NET developer. His commitment to quality through test driven development, vast knowledge of C#, HTML, CSS and JavaScript as well as his ability to mentor younger programmers and his passion for Agile/Scrum as defined by the Agile Manifesto and the Scrum Alliance will certainly be an asset to your organization.

Leave a Reply

4 Comments on "Unit Testing Angular(2+) with JSDOM"

Notify of
avatar
Sort by:   newest | oldest | most voted
trackback

[…] Unit Testing Angular(2+) with JSDOM (Dave M. Bush) […]

||370
Guest

Thanks!

chen
Guest

Thanks 4 sharing
1. we can use chrome-headless, so the problems that u repreduce is not exist anymore.
2. it will be intersting to see the diff time between chrome-headless and jsdom with karma on angular2

wpDiscuz