- know Ruby
- know Rspec
- want another mocking mechanism for Ruby with Rspec
Let's say you have a game class that you are trying to test:
class Game
def initialize(rules)
@rules = rules
end
def over?
return true if @rules.winner
return true if @rules.tied?
end
end
This is what the Rules class looks like:
class Rules
def initialize(board)
@board = board
end
def tied?
!winner && @board.filled?
end
def winner
@board.unique_marked_values.detect {|p| win?(p)}
end
private
def win?(player)
square_sets.any? do |squares|
squares.all? {|square| square == player}
end
end
def square_sets
@board.rows + @board.columns + @board.diagonals
end
end
Note there are 4 methods in Rules, but only two are public, tied? and winner. Assuming you want to mock Rules because it is too complicated to set up a tied or winner situation.- Create a mock folder in spec
- Create a mock file (you use the same name as your class file, in my case, rules.rb)
- In your mock file
- Require surrogate
- Endow the mock class
- Define the class methods
- Test if your mock is substitutable for the original class
#game/spec/game/rules_spec.rb require 'game/game' require 'game/rules' require 'mock/rules' describe Game do it "checks if MockRules is substitutable for rules" do MockRules.should be_substitutable_for(Rules) end end - Use the mock class
#game/spec/mock/rules.rb
require 'surrogate/rspec'
class MockRules
Surrogate.endow(self)
define(:tied?) do
false
end
define(:winner)
end
3. Define your class method
There are three way to define your methods:
- One line with return type
- Three lines with return type
- One line with default return
define(:tied?) {false}
define(:tied?) do
false
end
define(:tied?)What this method really returns is "nil" and since nil evaluate to false as well, you can think of it as false.
def winner(board) ..... endthen in your mock class, you can define the method as follow:
define(:winner) do |value|
value
end
or
define(:winner) {|value| value}
4. Test if the mock class is substitutable
This step is important because it ensure your class has the same methods/interface as the class you are mocking. This means if I add the method def name to Rules, when I run rspec, it will fail. Or if I decide to remove def winner in Rules, running the test will fail. Again, this restrict your MockRules to have only methods defined by Rules.
5. Use the mock class
Let's say you want to test that the game is over when there is a tied. Again, this is the game over method
def over?
return true if @rules.winner
return true if @rules.tied?
return false
end
For this test, you want to return true when rules.tied? is called and check to see if rules.winner is called. Here are the steps:- Create a mock class with method you want to override
- Create the class under test
- Set the mock to game
- Call the method under test
- Verify expected behaviors
- Verify over? is true
- Verify winner was called
it "is over when the game is tied" do
rules = MockRules.factory(tied?: true) #1. Create mock class with specified behavior
game = Game.new(rules) #2. Create a game class 3. "Set" mock to game
game.should be_over #4. Call method under test and 5. Verify expected behavior
rules.was asked_for :winner #5. Verify expected behavior
end
Note, some say that you should only test for one behavior in each test. I have not figure out where I stand on this argument, but this post is about using surrogate so it's easier for you to read if I put everything under one test. Surrogate can do a lot more. This is just a basic overview on how to use it.Resource

0 comments:
Post a Comment