Test sending emails with protocols
How do you test your system when it needs to send an email?
Imagine a working on a story card tracker, like pivotal or trello. A user can be assigned a card. Then the system should send them an email and let them know. Wait a minute … Our code needs to talk with the outside world!
How do we test its going to send the right email?
Lets gather some example code. A user can be assigned a card with the app.core/assign-card
function and it needs to send them an email.
We’d like to write a test checking that app.core/assign-card
sends
an email to the right address. Unfortunately the side effecting code is
further down in the system. It’s in another namespace and referred to
through notify/user-assigned!
.
The app.notify
namespace can be thought of as a global singleton. It
comes with all the advantages of global singletons, but also has the
downsides. The important downside for testing is the difficulty of
replacing it in order to change the functionality. We’d like to be
able to refer to the function, but then call some stub implementation
that doesn’t actually send an email.
Using a protocol allows us to switch the functionality
Using a protocol allows us to refer to the function, but pass in an argument that determines which implementation gets used. We can then create one implementation that uses the original functions to send email.
The actions required to replace the namespace with a protocol:
- Create a protocol with methods for each function used outside the namespace, ignoring any config arguments
- Create a record that will implement that protocol through the current functions, storing the config arguments in the record
- Change any callers of those functions to use the new protocol methods and call with a record instead of any config arguments
In the case of sending an email, we can make a Notify
protocol. Then
we create a record PostalNotifier
that takes the mail-host
config. Finally we change the system’s config to use the new record.
Create a stub implementation for the protocol
Adding this abstraction allows us to create a stub implementation of
Notify
. We can then create a config for testing that uses the
StubNotifier
and use that in our tests. Given this is a stub and
built for testing purposes, we can even reach inside to grab data and
examine it for correctness.
Most of the time using normal namespaced functions is great. Occasionally we’ll build some namespaces that are designed for side effects. In order to test the overall system we’ll want to replace that part, and a protocol will work out better. It allows us to provide stub implementations, and make sure that their functionality is being called as desired.