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