TypeScript unit tests best practices part 5: how to “unit test” (almost) everything in TypeScript

Thiago Oliveira Santos
10 min readFeb 20, 2020

--

Tips and techniques to create unit test to 99.9999% of TypeScript code

Psst!

You can find part 1 here: Introduction,
And part 2 here: IDE and Project Setup,
And part 3 here: definitions and rules
And part 4 here: clean test suites structure!

In a previous article, we took a pretty strict definition of unit test: the test of one single case of one single method, with everything else mocked. In many situations, this can be pretty challenging to achieve. We’ll try to cover some of these cases here to present some pretty simple strategies.

For the cases approached here, we used mocha, sinon, chai and sinon-chai to setup our test environment!

How to stub an exported function?

This is quite simple. If you followed the tips to setup your tsconfig.json, your module is setted to commonjs, and you’ll not have any problem with this strategy. Let’s take the following code:

To mock otherFunction, just import it with “* as”, like this:

Look that it doesn’t matter the real return type of otherFunction: all that matter is that myFunction returns wathever otherFunction returns. This is guaranteed by this test.

And why this works?

If you transpile your code and take a look in the generated JavaScript, you’ll see that the call to otherFunction will look something like this:

otherFunction_1.otherFunction(123)

So, an object that is the result of a require contains a reference to otherFunction. In the test code, importing it with “* as” you’re simpling having access to another reference to such object and, in result, having access to the same reference the tested code have to otherFunction. That’s it. The secret to stubbing is just to guarantee that the test code and the tested code are pointing to the same memory address.

How to stub a class constructor?

Imagine this code:

Well… what I have to test here, anyway? It’s just the instantiation of a class!

But this function can be called in many places, and in all these places you are confident that the function is making it’s work right:

  • Instantiating MyClass with the right parameters
  • Returning an instance of MyClass;

Isn’t it worth it to ensure that this work is made right, given the extensive usage?

The answer is yes, it always is. The goal is 100% coverage, so no code is not worth it of a unit test.

You have two strategies here:

  • Just check if the returned instance is a MyClass one: not very good. What the MyClass constructor does? Will you mock all the methods this constructor calls? How you’ll ensure that the right parameters are passed? Remember: you must not need to worry about the scope of method X when testing method Y, so, no, we do not recommend this option;
  • Stub MyClass constructor: Yes. This is the way.

But we know how to stub functions and methods, but how can I stub a constructor?

The answer is quite simple: in JavaScript, a class is nothing more than a function that returns an object based on a prototype. So, you can mock a class like any other function. Let’s see how we can do it here:

When you come from a strongly typed object oriented language, this can be seen like sorcery, but yes, you completely overwrote the class construction and returned a mocked object of your choice. This object, also, could contain many stubbed properties to simulate methods of MyClass, or can be something completely different. All depends on your need.

How to test a ts file without functions, just runnable code?

Let’s take this example:

How to test it, if I don’t have a function to call simulating both scenarios?

Well, the real problem here is the require cache. When a file is loaded by require, the exported result is saved in it’s cache. Then, in a next call of require to the same file, no code is ran. So, how to solve it? Deleting the cache, of course!

How to stub directly exported functions (module.exports = function, export = function)?

First of all, if you want to write a code exporting something like this, do not! You literally cannot stub this function. If, one day, you need to use some old library written that way, you just can’t stub it, simply because the reference to the point of memory of such function will always be different between the test code and the code to be tested. But, there is a solution: a wrapper!

Let’s take this example:

Look how we imported the function. Looks odd this import/require sintax, but it is the recommended way to import dependencies exported like that!

The untestableOldFunction, as we said, cannot be stubbed. If we import it in the test code, the memory reference never will be the same! So, how to solve it? We can wrap this reference in our own export, like this:

Look that we did a typecast for the function. We do that so no unexported type will be considered outside this wrapper. This signature doesn’t need to match exactly the untestableOldFunction one, it just need to match your need of use.

Now, we recommend to write a test to validate the wrapper:

Now, you have guaranteed, by unit test, that, if you use nowTestableOldFunction, you’ll use untestableOldFunction and, if someone change it in the future, the unit test will alert him!

Now, you need to change the import in the code you want to test:

That’s it! Now you have a code totally testable! I’ll not even put the example of the test here because you already have a pretty good example more above!

But it’s important to point out that, sometimes, a class can also be exported in an untestable way. The same strategy can be used to it, though, like that:

Look that, in this example, the cast for UntestableCass is not the interface that represent it’s instance. Why? As we said, because a class is just a function that returns an object based on a prototype. NowTestableClassType represents the type of the instance, of the prototype, not of the class itself.

How to stub global functions?

Imagine the following code:

We’ll show a pretty straight forward approach and the recommended one when you’re dealing with global timing functions, like this specific case. With a SinonFakeTimer, you can simulate the clock behavior, simulating time passing and, with that, triggering all the setted time events. A valid unit test for this method will look like this:

First, take a look in the behavior setting for the stubs: callThrough. As both stubbed functions will not run any external operation and they’re not part of your code, it’s not bad to let it maintain it’s own behavior, and that’s what callThrough does. Regardless, it is important to validate the parameters passed to it.

Now, look that setImmediate is triggered just by the call of the method tick with parameter 0. tick Simulates N milliseconds passing. But how setImmediate can be triggered if I’m saying that 0 ms has passed?

Well, like the name suggests, setImmediate callback will be evaluated at the first opportunity. No time need to pass for it, just the evaluation of events.

On the other hand, setTimeout callback needed 100 ms to be triggered, as it was setted to it.
Pretty simple, don’t you think?

Similar strategy can be used to others global functions, the important thing is: they can be stubbed!

Testing stream handler functions

For this one, the answer will no be too straight forward because the better test strategy depends on each case.

In fact, stream handlers functions can involve pretty nasty test cases. That’s because the solutions to the simplest stream mocks are pretty verbose. Also, a good test must be simple to read, must have a linear code and separated code blocks so the coder can see clearly what is the arranging, the action and the assertions steps for the test. In some cases, achieve such organization with stream without using any support library, is pretty much impossible.

For example, let’s take this piece of code:

The not recommended verbose approach

First, we’ll show a simple test case where the said organization is followed without the use of any support library.

Look that, we created a readable and a writable objects to mock the parameter and the result that createGZip stub returns. Those instances needed to have some pretty verbose implementations given such a small piece of code to be tested.

The simple stubbing recommended approach

For the piece of code in question, nonetheless, a simpler solution is just to follow the guidelines we gave more above and just write a test like this:

Why this is good enough?

Because you’re testing streamExample, not testing the native stream behavior. You just trust that it is already tested by the vendors. After all, this is a unit test, not an integrated one. But, for any reason, if you want to include native behavior to be executed in your test, maybe because you don’t trust that much the vendors or it’ll make the test simpler, there’s nothing wrong with it, just make sure that no external resource is being accessed, such a database, a file or an API, for example.

When things starts to get eventier

Maybe the function you’re testing, in place of doing just some pipening, uses stream events to implement different behavior, for example, transforming the result and returning a promise. This is a pretty common need and deserves a insight about it. Let’s take this piece of code:

This implementation is pretty complex, literally made to scare.

What it does is getting each chunk from the origin stream, gzipping it, then, returning an array with all the results.

In this scenario, both strategies we talked for stream will lead to a complex test (try it yourself and see!). So… which is the simplest strategy to test this code? For this one, we’ll finally use a support library: the stream-mock!

Look how simple the test became, each case even simpler than the code being tested.

Notice that the values resulted by the stubs are not even close to the real expected ones but, as we said, it doesn’t matter. It fits our function need and that’s enough!

Also, we tested the case when some error occurs in the process, which is pretty important too!

Stubbing event emitters

This is really really simple and works even for streams when you’re dealing with event listeners. Let’s take this example:

The easiest way to test this code is to create an EventEmitter and emit each event on separated test cases:

It has no secret, isn’t it? Notice that you could also do a single test case and emit both events, but do that just if it makes sense for you test case. Don’t do that to make your work easy because it really will not make big difference and the future maintenance will be worsened.

Stubbing chained methods calls (fluent interface)

There is some libraries that makes use of extensive method chained calls. Such libraries are knows to follow a concept called fluent interface. Promises with then and catch methods are a good example. Another popular example is the SuperAgent library. Take a look in this piece of code extracted directly from their site:

request
.post('/api/pet')
.send({ name: 'Manny', species: 'cat' })
.set('X-API-Key', 'foobar')
.set('Accept', 'application/json')
.then(res => {
alert('yay got ' + JSON.stringify(res.body));
});

This kind of code looks laborious to stub, because basically we need to have one stub returning an object with another method stubbed. There is some libraries, however, that helps to test this specific case, like nock. We want here, regardless of that, propose a “vanilla” way, using just mocha, sinon, chai and sinon-chai, to test this piece of code:

First, notice that we used our poetic license to change the alert for console.log. As our approach is for a back-end test, there is no window object, therefore, no alert method to use.

Also, we created a promise to be the result of the method post. Why? So the then method will behave as it should without the need of a stub. We could stub then, but we choose not to. Promises are a way to define the flow of execution, and stub their methods is just a way to over-complicate your test. In this specific case, as SuperAgentRequest is a PromiseLike object, so this approach is a simple way to resolve the flow.

Also, we created a stub for each used method, assigning them to properties in requestResult, and each stub returns the same object, requestResult. Why?

With this organization, no matter the order the methods are called, the test will pass. The only condition is that every one of the stubs must be called before then, and it is guaranteed by the typing and by this organization. It’s simple, clean, respects the AAA organization and it works! We can assert every call with it.

Particularly, I prefer this approach over nock for unit tests, but it is a matter of taste. The thing is that it works for every Fluent like solution you need to test.

But, what if the order of the calls matter?

Well, in this case, we can use the following approach:

It is equally simple and clean and if the order of the calls changes, the test will break.

And again: it works not only with SuperAgent, but with every Fluent like library.

How to test a code inside an anonymous function?

You may face a situation where a tested method have an anonymous function call that you just can’t cover in your test. How to do it, then?

First, let me point out that, when this situation is related to promises, check it out if no await is faulting in your test code and, if it is related to events, use the emit approach discussed more above.

Now, if it is something completely different, this technique can be useful. Let’s take this example:

How to test this if, then? Simple enough:

See? With the method callsfake we can completely mock the behavior of the stubbed method as we please, so we make it call the anonymous function, the first parameters that callbacker receives. Also, when asserting the call of callbacker, we used a sinon matcher just to make sure it received some function. We can’t be very assertive here as we don’t have a reference for the anonymous function, but this is enough and we could test both scenarios for our method!

That’s it!

I think that cover out the trickiest scenarios for stubbing! If you have some other situation, let us know and we’ll find a way to test it out!

--

--