Javascript Classes vs. Closures (3/3)

Testability

Richard Marmorstein
Engineering@Livestream
6 min readJun 21, 2017

--

This post is the last in a three-part series comparing two close substitutes in Javascript: classes and the “closure pattern” (also frequently known as the factory class pattern). Part one argued that closures have a lintability advantage. Part two argued that classes, on balance, have a performance advantage. This post will show an example of how classes can be easier to test.

Mocking and Monkey-patchings

At Livestream, we put a lot of effort into mocking. We incessantly mock not only each other but entities in our code. For the former, our sharp wits are sufficient; for the latter, we rely heavily upon the library Sinon.JS, and we love it.

Unfortunately, sometimes it can be ‘hard to reach’ a particular method inside your test, which makes mocking that method hard or impossible. (Except, not quite impossible — for better or worse, nothing is impossible in Javascript). In particular, a method is ‘hard to reach’ if you both:

  1. Don’t use dependency injection (DI). If you consistently use DI, you can inject any mock when you initialize what is being tested.
  2. AND you use closures instead of classes. If you use classes, all instances of your class share the method on the prototype, you can reference it from your test and override it with a mock. If you use closures, then your instance methods are inaccessible, and you cannot do this. It is possible in node.js, but it requires dark magic.

It’s example time!

Example 1: Dependency Injection with Closures

Suppose we have an HttpClient, implemented using the closure pattern.

Now suppose we have a model that uses the HttpClient, and expects it to be provided via DI.

Now suppose we want to write a test for the getById function. We want to mock our HttpClient class and make sure it is called as we expect it to be. This is easy! We construct a mock using our good friend Sinon.JS, and inject it.

Too easy!

Example 2: Non-DI with Closures

In this example, the FooModel is tweaked slightly to initialize its own HttpClient, instead of having it injected.

We can’t inject our mock function anymore. In fact, the only possible way to attach our mock to the fooClient it is to call upon the darker magicks and mess with the Node.js require cache. Something like this:

I did it by hand, which is painful — but this is actually a viable testing strategy. There’s a wonderful library called Mockery that gives you the tools to be able to do this more easily and helps you with some of the gotchas this approach has. However, overriding things in the require cache is a delicate art. Even with the right tools there is some mental and coding overhead into getting it right. It is better if you don’t have to in the first place.

Modifying the node.js require cache is best when done at the stroke of midnight.

If you wish to inject a mock into code written this way, but cannot afford a major refactor, the lightest touch is often to expose the subject of your mocking on the returned object:

The mock can be injected as follows:

This doesn’t seem too bad in a trivial example — but the ‘harder to reach’ the function is, the more awkward it will be to expose it.

We can avoid this problem altogether if we use classes.

Example 3: Non-DI with Classes

If the HttpClient used the class pattern instead of the closure pattern, it is easier to test. No need to do dark magic on the require cache , no need to dangle testing apparatus from all your objects — you can override the ‘get’ method directly on the HttpClient prototype.

See? No messing with the require cache necessary. Pretty straightforward. It can get tedious storing, overriding, and restoring every method you want to mock. Sinon has a syntax (sinon.stub(object, “method”)) which makes this a little more convenient. That’s not the solution I favor — more in a future blog post!

The takeaway from all this: if you use DI all the way down, mocking is a cinch. If you don’t, then your testing life might be harder if the mocked modules are closures instead of classes.

I hope this post and its two older siblings were informative, entertaining, and/or effective fodder for arguments with your colleagues!


Article too long? Follow me on Twitter.

Article too short? Stay tuned to the Livestream engineering blog for more.

Also, we’re hiring.

--

--

Richard Marmorstein
Engineering@Livestream

An autonomous generalized problem-solving and entertainment system, implemented chiefly in deoxyribonucleic acid, featuring a robust natural language interface.