Unit Testing And Seams

Unit Testing And Seams

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.

Dependency Injection

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.

Our SUT is composed of the DOC.

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.

In our test before we call the method(s) we want to test, we need to inject the Mock or Test Double.

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.

An alternative to traditional dependency injection is using method parameters. Here when we call the method we want to test, we pass in the Mock Object or Test Double as a parameter.

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.