When I write my Ruby tests, I’m a big fan of using test doubles and asserting those doubles receive messages with the correct arguments. I guess I like to practice what Justin Searls calls Discovery Testing.
One thing I’ve really enjoyed about Elixir is how easy it is to test, but I’ve also sort of missed the isolation that you get by using test doubles. Well, lucky for me, I’ve now seen what I think might be an even better version of that pattern, and it’s great for testing the edges of your application where you have to deal with the gnarly outside world (like IO).
I’ve been doing a big refactor on Benchee recently, and while working on that I ran across the following test:
I thought “Woah, that looks just like an assertion on an object receiving a message in Ruby! What black magic is happening here?” Well, of course it wasn’t black magic, but the closest thing we have to it in Elixir - OTP!
So, the secret lies in that
TestPrinter module that we’re passing to the
measure/2 function. In the actual code we’re passing in a
which has functions involved with printing stuff to the console. Of course we
want to ensure that this code is executed in our tests, but we don’t actually
want to print to the console the whole time. Also, exactly what is printed
isn’t really the scope of this test, so using
capture_io and doing some RegEx
magic on it to make sure stuff is printed would be duplicating test behavior.
So, instead we have this wonderful module:
What that module does is replicate the same interface as the actual
module, but instead of writing to the console, it sends messages to the current
process running that test. Then we can use the built in
function in ExUnit to assert that the current process received a message
matching what we put in our
FakeBenchmarkPrinter module. I’ve seen a few other
examples of dependency injection in tests in Elixir that allow you to test a
given function in isolation - I’ve even written about a similar thing before -
but this was the first time I’d seen anything like this.
Pretty cool,huh? I can’t take credit for this, though - props to Tobias Pfeiffer for this one!