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

Related Post

  • Unit Testing an Angular 2 CLI ProjectUnit Testing an Angular 2 CLI Project This week we want to continue our series about Angular 2 by looking at the Unit Testing capabilities that Angular 2 provides for us.  What we want to cover today is: Tweaking Karma to […]
  • Exposing Secret JavaScript privates to Unit TestsExposing Secret JavaScript privates to Unit Tests The question comes up all the time, “How do I access JavaScript privates from my Unit Tests?”  And invariably, the purist chimes in with the answer, “you don’t”. But, isn’t the point of […]
  • 100% Code Coverage Possible?100% Code Coverage Possible? In response to my post “Excuses For Not Testing” Kris K asked: There is also another side of Unit Tests. Some companies are so fixated they aspire to have 100% Unit Tests coverage and […]
  • Is Your Architecture Crippling Your Unit Testing?Is Your Architecture Crippling Your Unit Testing? Last week I wrote a post that talked about Unit Testing and the need to make sure you are only testing one particular unit of code at a time.  The post was well received.  But […]
  • Unit Test StructureUnit Test Structure One of the recurring reasons I hear from people for why they are not implementing unit test in their code is because it takes too long.  On one level I get that.  But, my […]

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.

One Pingback/Trackback