Tests should be readable and consist of the minimum information required to understand the test case. Extra data and boilerplate distract the reader mind and make it harder to understand how the system behave.
Reducing extra information and boilerplate can be done in many ways, one of them is to use test drivers which hold implementation details of “how” to run the unit under test and leave the “what” do the test. Another technique is to use test kits to offload the usage of external resources to a kit that provides clear APIs.
Drivers, test kits, and other techniques are very useful and very recommended, However, they can easily backfire with huge amount of boilerplate. For example, lets say you want to use come kind if a database. You need to start a connection and shut it down at the end by probably running the code in before
and after
blocks — code that is not directly related to the tests and there to support it.
The test becomes tedious and less readable. think of other usages that requires a lot more boilerplate and startup code. Moreover, to create another suit that uses the same boilerplate code you are going to copy / paste the same code which causes code duplication and makes the system harder to maintain.
Suite wrappers can help us remove the boilerplate that does not directly related to the code, but rather belongs to the test kits being used. Suit wrappers hold the preparation boilerplate and run it seamlessly.
The suit wrappers implementation is tightly coupled with testing frameworks such as mocha
, jest
, and jasmine
but provides clean API to the test. To achieve that, we are going to extend the suite with before
and after
statements that executes implicitly.
Suit wrapper consist of 3 parts:
The test framework wrappers provide functionality to create custom wrappers and export custom suits including the test kit functionality.For example, mocha wrappers:
In the example above we export an API that gets a conf
item which will hold the implementation we want to insert the suit. Given a beforeDesc
object we can run it before describe
blocks. The same goes for afterDesc
, beforeIt
and afterIt
.
The test kit wrappers use the test framework wrappers and provide test suit with extra functionality. For example, we can implement here the functionality to start and shut down the external database we want to use or any other functionality we want to run before and after tests.
The test suit uses the test kit wrappers and expose the context and a test kit object to the tests by simply use mySuit.describe
and mySuit.it
. The test gain the extra functionality without extra code and make the test leaner.
The test above looks neat and minimal, however a lot is going on under the hood. Before the test actually run, the test kit before
block executes, then the suit beforeEach
runs, then the test and finally the test kit after
block.
Suit wrappers, as test drivers and test kits, are tools in our toolkit. We should use them wisely as they can be an overkill for simple scenarios. Misuse of such can lead to over complexity and cause unmaintainable codebase.
To make a wise use of the tools, I recommend to write the tests without, then search for a tool that will actually make the code better. This is what we did when encountered a messy test — all other tools we tried didn’t make the job done and the test cleaner and finally we came up with suite wrappers.
Photo by Goh Rhy Yan on Unsplash
On the one hand, use of suit wrappers can lead to the usage of shared objects which can be easily created and make tests failed with no visible reason. It is up to the test kit wrappers to start and clean the test kit and maintain the context so such things wont happen. On the other hand, the suit wrappers can keep objects in memory and soft clean the environment between each test, hence run tests faster.
You can find all the examples here :https://gist.github.com/alonn24/e18fe297fcfd8ad5ca47ce020944d2d9
Thank you for reading! Have an opinion on the subject? please comment! I would love to hear about it.