In the Ruby world there are two primary testing frameworks, rspec and minitest. Rails prefers minitest, but it's not hard at all to swap it out with rspec. Full disclosure, I prefer rspec as of right now, and most of the applications I work with use rspec.
A few weeks ago I ran into an issue. I had an app that my team owned that did use minitest, and it was completely foreign. To complicate things it used Minitest::Spec. This threw me off because not all of the rspec things worked. However, I can see why people like it. Personally, I'd prefer using the core minitest way.
Anyway, I ran into a problem. How do you stub a method that's on a chain?
For example, car.get_in.buckle_up_buckaroo!
First, a note about doubles, stubs, dummies, mocks, spies, fakes, monkey patching, centurions, etc!
I was kidding about centurions. Those don't exist...yet. However, the others do, and they caused me grief. What's the difference? Why would I use one over the other?
Now I may update this as I learn more, but there aren't that many differences. Different frameworks have come up with different names for what they needed. That said, defining each one deserves it's own post. However, for the purposes of this post I'm going to use the definitions in the post by Ilija Eftimov.
The problem
I have something I want to test, but it depends on something else. Normally, I'd just stub the needed thing and move on, but what if that thing is owned by another thing. To make matters worse the application is spaghetti code heaven, and I can't trace where things are to do a proper stub? For me I'd rather stub the message chain.
Here is a contrived example.
class GrumpyGameFairy < StandardError
def initialize(msg="Outlook is cloudy, try again later")
super
end
end
class GameFairy
def proclamation
if mystic_visions == 'The game is in a stalemate!'
false
elsif mystic_visions == 'The game is won!'
true
else
raise GrumpyGameFairy
end
end
private
def mystic_visions
"Leave me alone!"
end
end
class GameFairyGateway
def self.get_fairy
game_fairy = GameFairy.new()
end
end
class Game
def done?
return GameFairyGateway.get_fairy.proclamation
end
end
Here we have a Game
, GameFairyGateway
, and a GameFairy
. The GameFairy
is a mysterious resource that we can't understand. How it works is beyond human comprehension. However, it tells us the state of the game (i.e. is the game done), but we fear asking it questions directly. To help us here we've added a GameFairyGateway
to be our messager for the GameFairy
's divine messages.
The problem is that we want to test the game, but let's say we can't efficiently stub the supernatural nature of the GameFairy
How do we do it?
receive_message_chain
rspec's require_relative '../game'
describe Game do
subject(:game) { Game.new() }
describe '#done' do
context 'the game fairy says there is a tie' do
it 'returns false' do
allow(GameFairyGateway).to receive_message_chain(:get_fairy, :proclamation) { false }
expect(game.done?).to be false
end
end
context 'the game fairy says the player_1 is the winner' do
it 'returns true' do
allow(GameFairyGateway).to receive_message_chain(:get_fairy, :proclamation) { true }
expect(game.done?).to be true
end
end
end
end
Here we are using 'receive_message_chain' to allow us to base the stub on GameFairyGateway
even though we are stubbing the proclamation
method on GameFairy
. Note that this is really only for legacy code that you're looking to refactor later but for now you can't. Please refer to rspec's documentation for more information.
minitest's solution.
require "minitest/autorun"
require_relative '../game'
class TestGame < Minitest::Test
def setup
@game = Game.new
@game_fairy = Object.new
end
def test_that_game_is_not_done
def @game_fairy.proclamation
false
end
GameFairyGateway.stub :get_fairy, @game_fairy do
assert_equal false, @game.done?
end
end
def test_that_game_is_done
def @game_fairy.proclamation
true
end
GameFairyGateway.stub :get_fairy, @game_fairy do
assert_equal true, @game.done?
end
end
end
This case is slightly different as we don't have an equivalent to the receive_chained_message
method. In order to do
something similar we need to create two stubs. The first uses the @game_fairy dummy and is where we stub out the proclamation method. The second is where we stub out the
get_fairymethod on
GameFairyGateway`. Note that these are
different ways to do the same thing.
You'd be correct in pointing out that we are stubbing out the thing that we "weren't able to understand". Unfortunately, we don't have another option, so we will have to use a dummy to represent the great power of the GameFairy.
Running the tests
Running the code here is pretty easy. The code can be found on my github
profile. Run git clone
to get the repo and ensure you have ruby,
minitest, and rspec installed.
From there run the rspec suite with:
rspec rspec/game_spec.rb
The minitest suite with:
ruby minitest/test_game.rb
Conclusion
receive_message_chain
is not a method that should be used in new code. It's a code smell that indicates unnecessary
complexity. minitest does not have an equivalent to it, but be wary of needing to stub out too many things in your unit
tests. More is not always better.