Martin Fowler says Mocks Aren’t Stubs and talks about Classical and Mockist Teting. Dave shows slightly amusing set of photos about “ists” – Rubyists etc. Ist bin ein red herring. The big issue here is when to use a mock.
Overview of Stubs and Mocks
Terminology: test double – an object standing in for a real object (like a stunt double).
customer = Object.new logger = Object.new customer.stub(:name).and_return('Joe Customer') logger.should_receive(:log) customer.should_receive(:name).and_return('Joe customer') # bad - very tightly bound to implementation customer.stub(:name).and_return('Joe customer') # also tighly bound to implementation
- Stubs are often used like mocks, mocks used like stubs.
- We verify stubs by checking state after an interaction.
- We tell mocks to verify interactions.
- Sometimes stubs just make the system run.
When are method stubs helpful?
Isolation from non-determinism: Simulate random value geneators or Time.now.
Isolation from external depedencies: e.g. external database or network. Have gave anexample or an ActiveMerchant test that takes 1.5s to run, and stubbed out gateway.stubs(:authorize).returns(AM:Billing:Response.new(true, ‘ignore’)
Polymorphic collaborators: e.g. employee that knows how to pay itself, uses a strategy. paymet_strategy = mock() employee = E.new(p_s)
When are messsage expectations helpful?
side effects: background processing
caching: only call a network zipcode lookup once
validator = mock() zipcode = Zipcode.new("01234", validator) validator.should_receive(:valid?).with("01234").once zipcode.valid? zipcode.valid?
interface discovery: tool to discover the parts of the system that you haven’t really worked out yet. Mock something out that doesn’t exist yet, while designing its interface.
All of these concepts are Isolation Testing – testing an object in isolation from others. This is a good fit when you have lots of little objects (ravioli code, as opposed to spaghetti code).
Isolation Testing in Rails
Rails is calzone code. Three layers: View Controller Model. These 3 layers are not the whole picture: browser, router, database. Standard rails testing:
- Unit tests: Testing in isolation. Test model classes (repositories), model objects, database.
- Functional tests: 2 or more non-trivial components work together. Test model classes, model objcets, database, views, controllers.
- Integration tests: Test model classes, model objects, database views controllers, routing/sessions.
This is !DRY
Mocking and stubbing you can do in Rails
Partials in view specs:
before :each do template.stub(:render).with(:partial => anything) end ... template.should_receive(:render).with(:partial => 'nav')
Conditional branches in controllers: Stub new and save! methods of models.
Dave has a new project stubble on github: You will need to build RSpec locally to use this for now.
stubbing(Registration) do # Stubs ActiveRecord finder and save methods on model
Chains are a new RSpec feature: user.stub_chain – some people say this is a test no-no, use with caution.
Guidelines, concerns & Common Pitfalls
- Keep things simple
- Try to avoid tight coupling
- Complex setup is a red flag for design issues
- Don’t stub and mock the object that you are testing
- Concern: impedes refactoring (but some say refactoring is improving design without changing behavior, so tests should not change. This really depends what level you are refactoring at).
- Concern: false positives