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.
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.
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
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.
All we need to do is to write our tests by simply creating a checker with a do nothing seam: