When you are writing unit tests, you will commonly need to write duplicate implementations of your dependencies to test against systems which do external communication or otherwise manipulate state that you can’t inspect. In other words, test fakes. However, a “test fake” is just one half of the component that you’re building: you’re also generally building a test inspector.
As an example, let’s consider the case of this record-writing interface that we may need to interact with.
1 2 3 4 5 6 |
|
This is a pretty simple interface; it can write out a record, and it can be closed.
Faking it out is similarly easy:
1 2 3 4 5 |
|
But this fake record writer isn’t very useful. It’s a simple stub; if our application writes any interesting records out, we won’t know about it. If it closes the record writer, we won’t know.
The conventional way to correct this problem, of course, is to start tracking some state, so we can assert about it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
This is a very common pattern in test code. However, it’s an antipattern.
We have exposed 2 additional, apparently public attributes to application code:
.records
and .closed
. Our original RecordWriter
interface didn’t have
either of those. Since these attributes are public, someone working on the
application code could easily, inadvertently access them. Although it’s
unlikely that an application author would think that they could read records
from a record writer by accessing .records
, it’s plausible that they might
add a check of .closed
before calling .close()
, to make sure they won’t get
an exception. Such a mistake might happen because their IDE auto-suggested the
completion, for example.
The resolution for this antipattern is to have a separate “fake” object, exposing only the public attributes that are also on the object being faked, and an “inspector” object, which exposes only the functionality useful to the test.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
In this refactored example, we now have a top-level entry point of
create_fake_writer
, which always creates a pair of WriterState
and
thing-which-is-like-a-RecordWriter
. The type of _FakeRecordWriter
can now
be private, because it’s no longer interesting on its own; it exposes nothing
beyond the methods it’s trying to fake.
Whenever you’re writing test fakes, consider writing them like this, to ensure that you can hand application code the application-facing half of your fake, and test code the test-facing half of the fake, and not get them mixed up.