Tuesday, February 9, 2016

Unit Testing Trick in JavaScript

C#, Java, C and most languages like them were built before the notion of unit testing became an industry standard. As such there are many problems you run into with these languages and testing. Many of these problems can be worked around by writing your code in a specific way. For example, using inversion of control (dependency injection) you can get around some problems. JavaScript was also created before unit testing became a major concern, but it supports unit testing in ways a language like C#/Java doesn't.

Normally when you test a function in C#/Java you have to create the entire class and do everything with class construction. This means you have to have a mock ready for every dependency the class has, even if you don't actually do anything with the dependency in your test. This causes lots of maintenance problems because every time you add a dependency you break a large number of tests. Since a lot of testing behavior has been copied from these languages we run into the same problem in JavaScript, but in JavaScript we have first class functions. We also have the ability to call functions with a specified context.

Here is the C#/Java way to test a function:

var object = new MyObject(mockedDep1, mockedDep2, mockedDep3, ...);
var result = object.myFunction(inputs);
Assert.SomeProperty(result);

Now here is a JavaScript way to isolate the function:

var functionUnderTest = MyObject.prototype.myFunction;
var fakedObject = {
        prop1: mockedProp
    }
var result = functionUnderTest.call(fakedObject, inputs)
Assert.SomeProperty(result)

Now which pattern makes sense depends a lot on the code. There are also a lot of tradeoffs. 

One argument against the second pattern is that this increases coupling between your code and your test which is a big problem with tests. Also the focus of the test becomes the function and not the object. This is bad if you want to focus on testing an object. Although you could say that it is better to make tests of small isolated pieces like functions. One example would be testing a stack implementation. Your tests will create the function in order to test multiple calls in a sequence.

The advantage of the second pattern is you are not having every test also test the constructor. Depending on how your code is structured your constructor may see a lot of churn as it adds dependencies. This tends to be a trait of a system using dependency injection. The contructor changes every time you add a depenedency. This requires you to change all your tests. Now you may have isolated out the constructor call so you only need to change one place, but you probably end up having lots of little exceptions since you may want to pass in something different.

One assumption the above code is making is that your function is attached to the prototype. Different object constructor patterns do things differently. Crockford don't not use the prototype while TypeScript heavily uses it.

Closures also add some level of complication also since you will modify data you do not have an easy grasp on.

The pattern of extracting the function and mocking the surrounding object is not universally the best pattern, but it is a tool in your testing arsenal that should be considered.

No comments:

Post a Comment