JavaScript unit tests
The amount of JavaScript code being used at Redgate is increasing quite rapidly. We are writing more web based products and also opting to use web technologies like CEF for our desktop products, so it is inevitable that a lot of our code is going to be written in JavaScript, like it or not.
The coding standards we’ve set mean that we spend a lot of writing unit tests for C# and getting the code coverage into the nineties, and I believe we should treat JavaScript with similar respect and love. Writing JavaScript unit tests is one good way of making our code robust and maintainable.
How?
A few weeks ago, I had a go at writing some unit tests using Jasmine for the DLM Dashboard (previously SQL Lighthouse) team. At that time, the DLM Dashboard team had just been brought together, so there was very little code to test. There was certainly a lot less JavaScript code to test than C#, and I was struggling to find JavaScript logic. On closer inspection, I found some JavaScript logic in one of the HTML files:
1 2 3 4 |
<img src="/Content/images/dbgreen.svg" data-bind="visible: State.Style() === 'success'" /> <img src="/Content/images/dbyellow.svg" data-bind="visible: State.Style() === 'deployed'" /> <img src="/Content/images/dbred.svg" data-bind="visible: State.Style() === 'drift'" /> <img src="/Content/images/dbred.svg" data-bind="visible: State.Style() === 'error'" /> |
DLM Dashboard uses the KnockoutJS library to dynamically update its views, and you can see KnockoutJS’ data-bind attribute being used above to decide which one of the four images to show to the user. Each image represents a particular state of the database, and since the database state is subject to change, we need to be able to update the image shown dynamically. It is better to think of the above snippet as a switch statement – only one of the four images is shown to the user, depending on the value of State.Style().
JavaScript Also Needs Unit Tests!
While KnockoutJS is a great library which makes it very easy to write dynamic DOM components, it is also very easy to use it to write code that is… not so readable. You may be using different JavaScript libraries in your application, but the point I am trying to make is that there is more JavaScript logic than you realise, and it’s often hidden in places you may not know about. In some cases, you might be able to refactor that logic into a separate function/module and start writing tests against it.
For example, in the situation I mentioned above, I could not write unit tests against the logic while it was in the HTML files, I had to refactor it into a JavaScript file like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var createViewModel = function () { this.getBadgeIconPath = function(status) { if (status === 'success') { return "/images/dbgreen.svg"; } else if (status === 'deployed') { return "/images/dbyellow.svg"; } else if (status === 'drift') { return "/images/dbred.svg"; } else if (status === ‘error’) { return "/images/dbred.svg"; } }; return this; }; |
I realise that this is a very simplistic example, but it is important to understand that I had also improved the JavaScript code before I actually wrote any unit tests. Just the intention of writing unit tests was enough to improve the code quality. And of course, once you’ve made your code testable then, for a simple example like this, you can write a simple Jasmine unit test like so:
1 2 3 4 5 6 7 |
describe("badge status colour", function() { it("uses the correct image for database drift", function () { var dashboardViewModel = createViewModel(); var iconPath = dashboardViewModel.getBadgeIconPath("drift"); expect(iconPath).toBe("/images/dbred.svg"); }); }); |
If you are already using a build runner like Gulp or Grunt, you can run unit tests as part of your Visual Studio build process. Writing unit tests and running them as part of your Visual Studio build process will allow you to catch regressions and bugs earlier, rather than a week later when looking at an obscure support request from a user.
However you decide to implement your unit tests, and for whichever technology, committing to the process and analytically thinking about how your code is laid out will immediately improve its quality.