Professional Documents
Culture Documents
Yourself Out
David Chelimsky
Articulated Man, Inc
http://martinfowler.com/articles/mocksArentStubs.html
Classical and
Mockist Testing
Classical and
Mockist Testing
Classical and
Mockist Testing
classicist mockist
merbist railsist
rspecist testunitist
ist bin ein
red herring
The big issue here
is when to use a
mock
http://martinfowler.com/articles/mocksArentStubs.html
agenda
๏ overview of stubs and mocks
๏ mocks/stubs applied to rails
๏ guidelines and pitfalls
๏ questions
test double
test stub
describe Statement do
it "uses the customer name in the header" do
customer = stub("customer")
customer.stub(:name).and_return('Joe Customer')
statement = Statement.new(customer)
logger.should_receive(:log).with(/Joe Customer/)
statement.print
end
end
mock object
describe Statement do
it "logs a message when printed" do
customer = stub("customer")
customer.stub(:name).and_return('Joe Customer')
logger = mock("logger")
statement = Statement.new(customer, logger)
logger.should_receive(:log).with(/Joe Customer/)
statement.print
end
end
mock object
describe Statement do
it "logs a message when printed" do
customer = stub("customer")
customer.stub(:name).and_return('Joe Customer')
logger = mock("logger")
statement = Statement.new(customer, logger)
logger.should_receive(:log).with(/Joe Customer/)
statement.print
end
end
mock object
describe Statement do
it "logs a message when printed" do
customer = stub("customer")
customer.stub(:name).and_return('Joe Customer')
logger = mock("logger")
statement = Statement.new(customer, logger)
logger.should_receive(:log).with(/Joe Customer/)
statement.print
end
end
mock object
describe Statement do
it "logs a message when printed" do
customer = stub("customer")
customer.stub(:name).and_return('Joe Customer')
logger = mock("logger")
statement = Statement.new(customer, logger)
logger.should_receive(:log).with(/Joe Customer/)
statement.print
end
end
method level
concepts
describe Statement do
it "logs a message when printed" do
customer = Object.new
logger = Object.new
customer.stub(:name).and_return('Joe Customer')
statement = Statement.new(customer, logger)
logger.should_receive(:log).with(/Joe Customer/)
statement.print
end
end
describe Statement do
it "logs a message when printed" do
customer = Object.new
logger = Object.new
customer.stub(:name).and_return('Joe Customer')
statement = Statement.new(customer, logger)
logger.should_receive(:log).with(/Joe Customer/)
statement.print
end
end
describe Statement do
it "logs a message when printed" do
customer = Object.new
logger = Object.new
customer.stub(:name).and_return('Joe Customer')
statement = Statement.new(customer, logger)
logger.should_receive(:log).with(/Joe Customer/)
statement.print
end
end
method stub
describe Statement do
it "logs a message when printed" do
customer = Object.new
logger = Object.new
customer.stub(:name).and_return('Joe Customer')
statement = Statement.new(customer, logger)
logger.should_receive(:log).with(/Joe Customer/)
statement.print
end
end
describe Statement do
it "logs a message when printed" do
customer = Object.new
logger = Object.new
customer.stub(:name).and_return('Joe Customer')
statement = Statement.new(customer, logger)
logger.should_receive(:log).with(/Joe Customer/)
statement.print
end
end
message expectation
describe Statement do
it "logs a message when printed" do
customer = Object.new
logger = Object.new
customer.stub(:name).and_return('Joe Customer')
statement = Statement.new(customer, logger)
logger.should_receive(:log).with(/Joe Customer/)
statement.print
end
end
things aren’t always
as they seem
describe Statement do
it "uses the customer name in the header" do
customer = mock("customer")
statement = Statement.new(customer)
customer.should_receive(:name).and_return('Joe Customer')
class Statement
def header
"Statement for #{@customer.name}"
end
end
describe Statement do
it "uses the customer name in the header" do
customer = mock("customer")
statement = Statement.new(customer)
customer.should_receive(:name).and_return('Joe Customer')
class Statement
def header
"Statement for #{@customer.name}"
end
end
describe Statement do
it "uses the customer name in the header" do
customer = mock("customer")
statement = Statement.new(customer)
customer.should_receive(:name).and_return('Joe Customer')
class Statement
def header
"Statement for #{@customer.name}"
end
end
describe Statement do
it "uses the customer name in the header" do
customer = mock("customer")
statement = Statement.new(customer)
customer.should_receive(:name).and_return('Joe Customer')
class Statement
def header
"Statement for #{@customer.name}"
end
end
describe Statement do
it "uses the customer name in the header" do
customer = mock("customer")
statement = Statement.new(customer)
customer.should_receive(:name).and_return('Joe Customer')
customer.should_receive(:name).and_return('Joe Customer')
class Statement
def header
"Statement for #{@customer.name}"
end
end
describe Statement do
it "uses the customer name in the header" do
customer = stub("customer")
customer.stub(:name).and_return('Joe Customer')
statement = Statement.new(customer)
class Statement
def header
"Statement for #{@customer.name}"
end
end
describe Statement do
it "uses the customer name in the header" do
customer = stub("customer")
customer.stub(:name).and_return('Joe Customer')
statement = Statement.new(customer)
class Statement
def header
"Statement for #{@customer.name}"
end
end
describe Statement do
it "uses the customer name in the header" do
customer = stub("customer")
customer.stub(:name).and_return('Joe Customer')
statement = Statement.new(customer)
class Statement
def header
"Statement for #{@customer.name}"
end
end
describe Statement do
it "uses the customer name in the header" do
customer = stub("customer")
customer.stub(:name).and_return('Joe Customer')
statement = Statement.new(customer)
board.place(piece, 48)
board.move(piece)
assert board.squares[48].contains?(piece)
end
end
time
describe Event do
it "is not happening before the start time" do
now = Time.now
start = now + 1
Time.stub(:now).and_return now
event = Event.new(:start => start)
event.should_not be_happening
end
end
isolation from
external dependencies
network access
Database
Interface Database
Subject
Network
Internets
Interface
network access
def test_successful_purchase_sends_shipping_message
ActiveMerchant::Billing::Base.mode = :test
gateway = ActiveMerchant::Billing::TrustCommerceGateway.new(
:login => 'TestMerchant', :password => 'password'
)
item = stub()
messenger = mock()
messenger.expects(:ship).with(item)
Stub
Database
Code Subject
Example
Stub
Network
network access
def test_successful_purchase_sends_shipping_message
gateway = stub()
gateway.stubs(:authorize).returns(
ActiveMerchant::Billing::Response.new(true, "ignore")
)
item = stub()
messenger = mock()
messenger.expects(:ship).with(item)
payment_strategy.expects(:pay)
employee.pay
end
end
mixins/plugins
describe AgeIdentifiable do
describe "#can_vote?" do
it "raises if including does not respond to birthdate" do
object = Object.new
object.extend AgeIdentifiable
expect { object.can_vote? }.to raise_error(
/must supply a birthdate/
)
end
logger.should_receive(:log).with(/Joe Customer/)
statement.print
end
end
caching
describe ZipCode do
it "should only validate once" do
validator = mock()
zipcode = ZipCode.new("02134", validator)
validator.should_receive(:valid?).with("02134").once.
and_return(true)
zipcode.valid?
zipcode.valid?
end
end
interface discovery
describe "thing I'm working on" do
it "does something with some assistance" do
thing_i_need = mock()
thing_i_am_working_on = ThingIAmWorkingOn.new(thing_i_need)
thing_i_need.should_receive(:help_me).and_return('what I need')
thing_i_am_working_on.do_something_complicated
end
end
isolation testing
specifying/testing
individual
objects in isolation
good fit with lots of
little objects
all of these
concepts apply to
the non-rails
specific parts of
our rails apps
isolation testing the
rails-specific parts
of our applicationss
M V
C
View
Controller
Model
Browser
Router
View
Controller
Model
Database
rails testing
๏ unit tests
๏ functional tests
๏ integration tests
rails unit tests
๏ model classes (repositories)
๏ model objects
๏ database
rails functional tests
๏ model classes (repositories)
๏ model objects
๏ database
๏ views
๏ controllers
rails functional tests
๏ model classes (repositories)
๏ model objects
๏ database
๏ views
๏ controllers
rails functional tests
๏ model classes (repositories)
๏ model objects
๏ database
!DRY
๏ views
๏ controllers
rails integration tests
๏ model classes (repositories)
๏ model objects
๏ database
๏ views
๏ controllers
๏ routing/sessions
rails integration tests
๏ model classes (repositories)
๏ model objects
๏ database
๏ views
!DRY
๏ controllers
๏ routing/sessions
the BDD approach
inherited from XP
customer specs
developer specs
rails integration tests
+ webrat
shoulda, context,
micronaut, etc
customer specs are
implemented as
end to end tests
developer specs
are implemented as
isolation tests
mocking and
stubbing
with rails
partials in view specs
describe "/registrations/new.html.erb" do
before(:each) do
template.stub(:render).with(:partial => anything)
end
http://github.com/dchelimsky/stubble
stubble
describe "POST create" do
describe "with valid attributes" do
it "redirects to list of registrations" do
stubbing(Registration) do
post 'create'
response.should redirect_to(registrations_path)
end
end
end
end
stubble
describe "POST create" do
describe "with invalid attributes" do
it "re-renders the new form" do
stubbing(Registration, :as => :invalid) do
post 'create'
response.should render_template('new')
end
end
assigns[:friend].should equal(friend)
end
end
chains
describe UsersController do
it "GET 'best_friend'" do
friend = stub_model(User)
User.stub_chain(:find, :friends, :favorite).
and_return(friend)
assigns[:friend].should equal(friend)
end
end
guidlines, pitfalls, and
common concerns
focus on roles
Mock Roles, not Objects
http://www.jmock.org/oopsla2004.pdf
keep things simple
avoid tight coupling
complex setup is a
red flag for design
issues
don’t stub/mock the
object you’re testing
impedes
refactoring
:refactoring => <<-DEFINITION
Improving design
without
changing behaviour
DEFINITION
what is behaviour?
false positives
describe RegistrationsController do
describe "GET 'pending'" do
it "finds the pending registrations" do
pending_registration = stub_model(Registration)
Registration.should_receive(:pending).
and_return([pending_registration])
get 'pending'
assigns[:registrations].should == [pending_registration]
end
end
end
http://xunitpatterns.com/
growing object-oriented
Mock Roles, not Objects software, guided by tests
http://www.jmock.org/oopsla2004.pdf http://www.mockobjects.com/book/
http://blog.davidchelimsky.net/
http://www.articulatedman.com/
http://rspec.info/
http://cukes.info/
http://pragprog.com/titles/achbd/the-rspec-book
ruby frameworks
rspec-mocks
http://github.com/dchelimsky/rspec
mocha
http://github.com/floehopper/mocha
flexmock
http://github.com/jimweirich/flexmock
rr
http://github.com/btakita/rr
not a mock
http://github.com/notahat/not_a_mock