In his recent look at Mocha’s internals, Brian Guthrie mentioned that he hadn’t realised it was possible to configure Mocha to warn or disallow mocking of non-existent methods. So I thought it was time I explained Mocha’s configuration settings. These configuration settings are all somewhat experimental and feedback on their effectiveness would be welcome.
There are 3 levels for all of the existing conditions :-
# default behaviour Mocha::Configuration.allow(condition)
# warning is displayed when condition is met Mocha::Configuration.warn_when(condition)
# error is raised when condition is met Mocha::Configuration.prevent(condition)
Stubbing a non-existent method
This is useful if you want to ensure that methods you’re mocking really exist. A common criticism of unit tests with mock objects is that such a test may (incorrectly) pass when an equivalent non-mocking test would (correctly) fail. While you should always have some integration tests, particularly for critical business functionality, this Mocha configuration setting should catch scenarios when mocked methods and real methods have become misaligned.
class Example end
class ExampleTest < Test::Unit::TestCase def test_example example = Example.new example.stubs(:method_that_doesnt_exist) # => Mocha::StubbingError: stubbing non-existent method: # => #<Example:0x593760>.method_that_doesnt_exist end end
Stubbing a method unnecessarily
This is useful for identifying unused stubs. Unused stubs are often accidentally introduced when code is refactored.
class ExampleTest < Test::Unit::TestCase def test_example example = mock('example') example.stubs(:unused_stub) # => Mocha::StubbingError: stubbing method unnecessarily: # => #<Mock:example>.unused_stub(any_parameters) end end
Stubbing a non-public method
Many people think that it’s good practice only to mock public methods. This is one way to prevent your tests being too tightly coupled to the internal implementation of a class. Such tests tend to be very brittle and not much use when refactoring.
class Example def internal_method; end private :internal_method end
class ExampleTest < Test::Unit::TestCase def test_example example = Example.new example.stubs(:internal_method) # => Mocha::StubbingError: stubbing non-public method: # => #<Example:0x593530>.internal_method end end
Stubbing Method on Non-Mock Object
If you like the idea of mocking roles not objects and you don’t like stubbing concrete classes, this is the setting for you. However, while this restriction makes a lot of sense in Java with its explicit interfaces, it may be moot in Ruby where roles are probably best represented as Modules. Anyway that’s probably a discussion for another day.
class Example def example_method; end end
class ExampleTest < Test::Unit::TestCase def test_example example = Example.new example.stubs(:example_method) # => Mocha::StubbingError: stubbing method on non-mock object: # => #<Example:0x593620>.example_method end end