1. Testing using Mock Objects tests implementation and duplicates code
describe ShowController, "index" do
describe "when a TV show has no public videos" do
it "should not show that TV show" do
Show.should_receive(:all).
with(:select => "id, name, public_videos_count",
:conditions => "shows.public_videos_count > 0").
and_return([])
get 'index'
response.body.should_not match(/#{@show.name}/)
end
end
end
1. Testing using Mock Objects tests implementation and duplicates code
class ShowController
def index
@shows = Show.all(
:select => "id, name, public_videos_count",
:conditions => "shows.public_videos_count > 0")
end
end
2. Mock Objects are brittle and do a bad job testing
module Codebreaker
describe Game do
describe "#start" do
it "sends a welcome message" do
output = double('output')
game = Game.new(output)
output.should_receive(:puts).with('Welcome to Codebreaker!')
game.start
end
end
end
end
2. Mock Objects are brittle and do a bad job testing
module Codebreaker
class Game
def initialize(output)
@output = output
end
def start
@output.puts("Welcome to Codebreaker!")
end
end
end
2. Mock Objects are brittle and do a bad job testing
Refactor to:
module Codebreaker
class Game
def initialize(output)
@output = output
end
def start
@output.write("Welcome to Codebreaker!") #changed to write
end
end
end
The test fails, even though the output is the SAME!
Mocks are stupid!!!
Mocking is not primarily about testing
Mocking is primarily about object oriented design.
Mocking is primarily about emergent oo design.
Mocking is primarily about emergent OO design.
Guided by Tests
A class should have only one reason to change.
class Modem
...
def dial(phone_number) end
def hangup end
def send(data) end
def receive end
end
What's wrong with this?
class Modem
...
def dial(phone_number)
@connection.dial(phone_number)
end
def hangup
@connection.hangup
end
def send(data)
@data_channel.send(data)
end
def receive
@data_chanel.receive
end
end
describe Modem do
describe '#dial' do
it "delegates to it's connection" do
connection = double('connection)
modem = Modem.new(:connection => connection)
phone_number = '1234567890'
connection.should_receive(:phone_number).with(phone_number)
modem.dial(phone_number)
end
end
end
Only talk to your immediate friends.
A class should never chain messages past a single layer
class Account
...
def childrens_names
@owner.children.map{|child| child.name}.join(',')
end
end
Whats wrong with this?
class Account
...
def childrens_names
@owner.childrens_names
end
end
describe Account do
describe '#childrens_names' do
it 'delegates to the account owner to get childrens names' do
owner = double('owner')
account = Account.new(:owner => owner)
owner.should_receive(:childrens_names)
account.childrens_names
end
end
end
Mocking is about discovering your domain
Tell collaborators what you need from them
Push decisions about implementation outside your responsibility
Express those decisions in domain terms
When needing to mock within a class, the tests are telling you to extract another class
A mock should not represent an object but a role that the object will play
You should be able to switch the implementation of the role, so long as the interface doens't change
1. Testing using Mock Objects tests implementation and duplicates code
describe ShowController, "index" do
describe "when a TV show has no public videos" do
it "should not show that TV show" do
Show.should_receive(:all).
with(:select => "id, name, public_videos_count",
:conditions => "shows.public_videos_count > 0").
and_return([])
get 'index'
response.body.should_not match(/#{@show.name}/)
end
end
end
What is wrong here?
1. Testing using Mock Objects tests implementation and duplicates code
describe ShowController, "index" do
it "delegate to the shows collection to find which have public videos" do
Show.should_receive(:with_public_videos)
get 'index'
end
end
And implement "with_public_videos" in your model layer
2. Mock Objects are brittle and do a bad job testing
module Codebreaker
describe Game do
describe "#start" do
it "sends a welcome message" do
output = double('output')
game = Game.new(output)
output.should_receive(:puts).with('Welcome to Codebreaker!')
game.start
end
end
end
end
What is wrong here?
2. Mock Objects are brittle and do a bad job testing
module Codebreaker
describe Game do
describe "#start" do
it "delegates to it's output to send a welcome message" do
output_interface = double('output_interface')
game = Game.new(output_interface)
output_interface.should_receive(:output).with('Welcome to Codebreaker!')
game.start
end
end
end
end