How to modularize test doubles

The ever-growing number of files does not spare test files. The number of similar test double files can be used as an indication of a need to refactor or modularize, test doubles. This blog applies the same techniques we used to modularize other layers of a nodejs application, but in an automated testing context.

In this article we will talk about:

Even though this blog post was designed to offer complementary materials to those who bought my Testing nodejs Applications book, the content can help any software developer to tuneup working environment. You use this link to buy the book. Testing nodejs Applications Book Cover

Show me the code

var should = require('should');
var expect = require('expect');
var chai = require('chai');

Example:

What can possibly go wrong?

The following points may be a challenge when modularizing test doubles:

In the next sections, we make a case on modularization for reusability as a solution to reduce code duplication.

The Status Quo

Every test double library is in fact an independent library. That remains true even when some libraries are bundled and shipped together, as is the case for chai(ships with should and expect). Every mock, every spy, and every stub we make in one place can potentially be replicated to multiple other places that test similar code-blocks, or code-blocks that share dependencies.

One of the solutions to share common test double configurations across multiple test cases is to organize test doubles in modules.

The need to have test doubles in tests.

In these series, there is one blog that discusses the difference between various test doubles: spy/mock/stubs/fake and fixtures. For the sake of brevity, that will not be our concern for the moment. Our concern is to reflect on the why we should have test doubles in the first place.

From the time and cost perspective, It takes longer to load one single file. It would take even longer to load multiple files, be in parallel or sequentially. The higher the number of test cases spanning multiple files, the slower the test runner process will take to complete execution. This adds more execution time, to an already slow process.

If there is one of amongst other improvements that would save us time, reusing the same library quite often while mimicking implementation of other things we don't really need to load(mocking/etc.), would be one of them.

Testing code acts as a state machine, or pure functions, every input results in the same output. Test doubles are essentially tools that can help us save time and cost as a drop-in replacement of expected behaviors.

How utilities relate to fixtures

In this section, we pause a little bit to answer the question: “How utilities library relates to fixtures library”.

Utility libraries(utilities) provide some tools that are not necessarily related to the core business of the program, but necessary to complete a set of tasks. The need to have utilities is not limited to business logic only, but also to testing code. In the context of tests, the utilities are going to be referred to as fixtures. Fixtures can have computations or data that emulates a state under which the program has to be tested.

Grouping imports with unified export library

The module system provided by the nodejs is a double-edged sword. It presents opportunities to create granular systems, but repetitive imports weakness the performance of the application.

To reduce repetitive imports, we make good use of the index. This compensates for our rejection to attach modules to the global object. It also makes it possible to abstract away the file structure: one doesn't have to know the whole project's structure to import just one single function.

How to modularize fixtures of spies

The modularization of spies takes one step in general. Since the spies already have a name, It makes sense to group them under the fixture library, by category or feature, and export the resulting module. The use of the index file makes it possible to export complex file systems via one single import(or export depending on perspective).

How to modularize fixtures of mock data

Mock data is the cornerstone to simulate desired test state when one kind of data is injected into a function/system. Grouping related data under the same umbrella makes sense in most cases. After the fact, it makes sense to manifest data via export constructs.

How to modularize fixtures of fakes

Fakes are functions similar to implementation they are designed to replace, most of the time third-party functionality, that can be used to simulate original behavior. When two or more fakes share striking similarities, they become good candidates for mergers, refactoring, and modularization.

How to modularize fixtures of stubs

Stubs are most of the time taken as mocks. That is because they tend to operate in similar use cases. A stub is a fake that replaces real implementations, and capable of receiving and producing a pre-determined outcome using mock data. The modularization will take a single step, in case the stub is already named. The last step is to actually export and reveal/expose the function as an independent/exportable function.

How to modularize test doubles for reusability

Test doubles are reusable in nature. There is no difference between designing functions/classes and test doubles for reusability per se. To be able to reuse a class/function, that function has to be exposed to the external world. That is where export construct comes into the picture.

How to modularize test doubles for composability

The composability on the other side is the ability for one module to be reusable. For that to happen, the main client that is going to be using the library has to be injected into the library, either via a thunk or similar strategy. The following example shows how two or more test doubles can be modularized for composability.

Some Stubbing questions we have to keep in mind – How do Stubbing differ from Mocking – How to Stubbing differs from Spying: Spies/Stubs functions with pre-programmed behavior – How to know if a function has been called with a specific argument?: For example: I want to know the res.status(401).send() — more has been discussed in this blog as well: spy/mock/stubs/fake and fixtures

Making chai, should and expect accessible

The approach explained below makes it possible to make pre-configured chai available in a global context, without attaching chai explicitly to the global Object.

var chai = require('chai');
module.exports.chai = chai; 
module.exports.should = chai.should; 
module.exports.expect = chai.expect; 

Example:

Conclusion

Modularization is a key strategy in crafting re-usable composable software. Modularization brings elegance, improves performance, and in this case, re-usability of test doubles across the board.

In this article, we revisited how to test double modularization can be achieved by leveraging the power of module.exports( or export in ES7+). The ever-increasing number of similar test double instances make them good candidates to modularize, at the same time makes it is imperative that the modularization has to be minimalistic. That is the reason why we leveraged the index file to make sure we do not overload already complex architectures. There are additional complimentary materials in the “Testing nodejs applications” book, on this very same subject.

References

tags: #snippets #nodejs #spy #fake #mock #stub #test-doubles #question #discuss