Announcements and Double Agents
Smalltalk had one of the first implementations of the
Observer design pattern.
Over the years, Visualworks Smalltalk has had several different implementations of
this pattern, from the original #changed
/#update
approach, to
#triggerEvent:
, to
Announcements.
I’ve migrated almost all of my code to Announcements over the past few years. When I recently started using mocks and stubs in my unit tests and wrote DoubleAgents to help with that, I quickly ran into a dilemma: How do I test a complex object that announces?
For a simple announcer, I’ll just test it directly with no mocking and
stubbing. Or, I might inject a direct instance of Announcer
if the
announcements are all I need to test.
For a complex collaborator that isn’t an announcer, I’ll use DoubleAgents and make a test double for the collaborator. I can stub and mock what I need to in order to write my tests.
But what if the code I’m testing needs to do both? I recently had a
case where I could easily inject the collaborator, but the code under
test needed to send a configuration message to that collaborator
immediately. The collaborator was also an Announcer
, and I needed
to test that my object responded appropriately to those announcements.
First, I’ll sketch out the code I’d like to be able to test. Normally, I use TDD and write the tests first, but for instructional purposes, let’s look at the code first.
Per Sandi Metz, let’s look at the messages involved in testing this code:
-
#collaborator:
is an incoming public method on the class under test; it should be tested. -
#configureFrom:
is an outgoing command message from the class under test; we need to test that it gets sent properly, but we don’t need to test for its effects here. We do that when we test the collaborator. -
#when:send:to:
is an outgoing command message from the class under test; we need to test that it gets sent properly. -
#interestingEvent
is an incoming method. I consider it to be a private method, becauseClassUnderTest
is the only place the name of the method will appear. However, it could also be considered to be a public incoming message since it is ultimately sent by the announcement framework.
One way to test this would be to test the individual methods separately:
This approach works, and tests all of the messages we care about, but it seems like some private implementation details are leaking out of the class and into the tests, so I’m not completely happy with it. If I decide to change how I subscribe to the announcement, or how I choose to implement the response, the test will have to change.
To make the test less fragile, I’d prefer to test the net effect of the announcement having occurred rather than the mechanism by which the effect occurs.
Given that, what I chose to do instead is to
extend DoubleAgents
to allow a DoubleAgent
to #beAnnouncer
. Using this new feature, I
can instead write the tests this way:
Using this approach, the tests don’t need to know any details about
how ClassUnderTest
chooses to respond to SomeAnnouncement
.
Instead, it only cares about the externally visible effects of the
response. This seems to me to be more maintainable and future-proof.
I added #beAnnouncer
in DoubleAgents version 9.