I learned about seams after reading Micheal Feathers’s book Working Effectively with Legacy Code. In essence a seam is a way to circumvent code that makes testing hard or even impossible.
For example, lets say we have a class that checks if a given task is valid. For reasons that do not interest us that same class makes a connection to another service and sends some data to it. That connection alone makes the class hard to test since we need to have and maintain a connection to that service during testing:
In this example, isNotAssigned()
makes the necessary checks but also sends the task to TaskAssigner
so if we want to write some tests for TaskChecker
we need to make sure that assigner is up and running.
Object seams
According to Mr Feathers there are three types of seams. The one that fits our case is called object seam and we are going to use it in order to bypass entirely making a connection and talking to the assigner.
Following the book’s example we end up with this:
which does exactly what we want since it provides a way to write tests that do not involve the assigner. We just need to use TestingTaskChecker
in our tests and we are good to go.
The downside with this approach is that we had to open our class which might not meet the project’s standards.
Function reference
Lets see what we can do without opening the class.
Just like before we need to extract the behavior that we want to override to its own method but this time we are also going to assign this method to a value and use the value in the calling site:
isNotAssigned()
will keep talking with the assigner only this time it does it through safeSendTaskToAssigner
.
Default value
Having this function reference means that we can force isNotAssigned()
to change its behavior by simply assigning a new value to safeSendTaskToAssigner
! And this is what we are going to do:
By default the seam is null which leads in having safeSendTaskToAssigner
referencing the original behavior allowing the entire project to keep working as before without any additional changes to other files.
If now we pass a non null value then it gets assigned to safeSendTaskToAssigner
and ends up being called instead of sendTaskToAssigner
. This way we remove the communication from our flow allowing us to finally write some tests.
Testing
All we need to do is to write our tests by simply creating a checker with a do nothing seam: