One of the topics I found very interesting form the Art Of Unit Testing was seams. I wanted to write a blog post to explore that a little bit.
What is a Seam?
Seams are places in your code where you can plug in different functionality …Art of Unit Testing, 2nd edition page 54
Seams are a technique to help us test hard-to-test code. They allow us to replace production functionality with different functionality while testing. We typically do this by replacing a real dependency with a test double or mock. We do this because the real dependency is something we can’t directly control from the test code. Examples include files, databases, network communications.
How do I add a seam into my program?
There are various ways to add seams to your program. They all revolve around inheritance. Adding a seam is simply exposing an object so that it can be replaced with a child object. Here are 2 common methods.
The simplest way to add a seam is to use regular old dependency injection. In this case, our Software Under Test (SUT) is composed of an interface for a Depended On Component (DOC). To add a seam all we have to do is provide a property node to set the DOC. Now we can specify either the real DOC or a mock object or test double.
In our test, we can use the property node to set the SUT to use a test double or mock instead of the real DOC. Then after that, every time the SUT calls a method on the DOC it will use the test double or mock instead.
A real-world example might be a driver for an instrument. It is probably composed of a serial port. You could easily compose the instrument of a serial interface instead. Then at test time, you could easily sub in a mock serial port.
As A Method Parameter
There is an alternative to having the SUT be composed of the DOC. The DOC can be passed in as a parameter to a method. This can work well when using the Strategy Pattern. In the test, we just pass in Mock Object or Test Double instead of the concrete class used in production.
A real-world example might be an object that gets saved to a file. Since the file is only really used when saving (and maybe loading) the object, it doesn’t make sense to use composition. The save method could take a generic file interface as a parameter. Then at test time, you could bypass the file system by injecting a mock object or test double.