Mocha 0.9 Released

Posted by James Mead Tue, 24 Jun 2008 19:14:00 GMT

There’s been quite a bit of work going on in Mocha over recent months, but a release is long overdue. The API is now pretty stable and so this release jumps from version 0.5 to 0.9. Much of the work has been refactoring Mocha’s internals to support new features and make the code more maintainable.

Before attempting the refactoring, extensive acceptance tests were added. One of the benefits of this is that it should now be easier to write new acceptance tests if you want to suggest new features or illustrate a bug ;-)

Here’s a quick summary of the changes in the release. I’ll try to post some code examples here in the near future.

Ordering constraints

Based on the JMock constraints with the same names…

Configurable warnings or errors

  • When a method on a non-public method is stubbed
  • When a method on a non-existent method is stubbed
  • When a method on a non-mock object is stubbed (partial mocking)
  • When a method is stubbed unnecessarily (i.e. the stubbed method is not called during the test)

See Configuration for more details.

Improved error messages

  • A more readable and complete list of unsatisfied expectations, satisfied expectations and state machines.
  • Display more sensible failure message for any_instance expectations.

Parameter matchers

  • New to this release: optionally (allows matching of optional parameters if available), yaml_equivalent (allows matching of YAML that represents the specified object), responds_with (tests the quack not the duck).
  • Nesting of matchers is now supported.

Syntax shortcut

An optional block can be passed into the Standalone#mock method. The block is evaluated in the context of the new mock instance and can be used as a shortcut to set up expectations.

Ruby & Rails compatibility

Tested with Ruby 1.8.4, 1.8.5, 1.8.6 & 1.9. All related bugs and warnings believed to be fixed.

Tested with Rails 1.2.3 & Rails 2.1.0.

Deprecation

There is no longer any need to have a “require ‘stubba’” statement in your code. A deprecation warning has been added to this effect, because the file will be removed in a future release.

Bug fixes

Posted in  | Tags , , , , , ,  | no comments

Mock Commands, Stub Queries

Posted by James Mead Fri, 03 Aug 2007 10:10:00 GMT

Zach Moazeni has just posted a suggested patch for Mocha over on his blog. My understanding of the patch is that it means expectations are verified even when an assertion error occurs in the test. Here is his example…


  class Car

    def initialize(parts = [])
      @parts = parts
    end

    def start
      started = true
      @parts.each do | part |
        # commenting out for failure
        # started = started && part.start
      end

      started
    end

  end

  class SomeTest < Test::Unit::TestCase

    def test_start
      engine_mock = mock("engine_mock")
      car = Car.new([engine_mock])

      engine_mock.expects(:start).returns(false)
      assert !car.start
    end

  end

I’ve had a friendly & useful conversation with Zach about it, but I’m not convinced this is the right way to go. Using the one assertion per test school of thought, you can achieve the same goal by splitting the test into two so you get a test failure for the expectation and another for the assertion…


  class SomeTest < Test::Unit::TestCase

    def test_should_start_engine
      engine = mock('engine')
      car = Car.new([engine])

      engine.expects(:start)

      car.start
    end

    def test_should_start_if_engine_starts
      engine = stub('engine')
      car = Car.new([engine])

      engine.stubs(:start).returns(false)

      assert !car.start
    end

  end

Something that makes the example less suitable for mocking is that the Car#start method is both a command and a query. If you separate the two, testing with mocks might be easier…


  class Car

    def initialize(parts = [])
      @parts = parts
    end

    def start
      @parts.each { |part| part.start }
    end

    def started?
      @parts.all? { |part| part.started? }
    end

  end

  class SomeOtherTest < Test::Unit::TestCase

    def test_should_start_engine
      engine = mock('engine')
      car = Car.new([engine])

      engine.expects(:start)

      car.start
    end

    def test_should_not_be_started_if_engine_is_started
      engine = stub('engine')
      car = Car.new([engine])

      engine.stubs(:started?).returns(false)

      assert !car.started?
    end

  end
  

I’d be interested to know what other people think…

One thing I do agree with Zach about is that submitting a suggested patch to an open source project is a great way of initiating a constructive conversation.

Tags , , , , , , , ,  | 4 comments

Mock Objects in Ruby

Posted by James Mead Sun, 22 Jul 2007 20:48:00 GMT

As promised I’ve finally got round to making the S5 slides of my LRUG talk available here.

You can use various keys to navigate the presentation. If you just want to quickly scan through the content, you might find the outline view useful (press the “T” key to toggle between slideshow & outline modes).

Tags , , , , , ,  | no comments

Mocha 0.5 released

Posted by James Mead Fri, 08 Jun 2007 15:46:00 GMT

  sudo gem install mocha

or download one of the latest packages from rubyforge.

Parameter Matchers

I’ve added a few Hamcrest-style parameter matchers which are designed to be used inside Expectation#with. The following matchers are currently available: anything(), includes(), has_key(), has_value(), has_entry(), all_of() & any_of(). More to follow soon. The idea is eventually to get rid of the nasty parameter_block option on Expectation#with.

  object = mock()
  object.expects(:method).with(has_key('key_1'))
  object.method('key_1' => 1, 'key_2' => 2)
  # no verification error raised

  object = mock()
  object.expects(:method).with(has_key('key_1'))
  object.method('key_2' => 2)
  # verification error raised, because method was not called with Hash containing key: 'key_1'

Values Returned and Exceptions Raised on Consecutive Invocations

Allow multiple calls to Expectation#returns and Expectation#raises to build up a sequence of responses to invocations on the mock. Added syntactic sugar method Expectation#then to allow more readable expectations.

  object = mock()
  object.stubs(:method).returns(1, 2).then.raises(Exception).then.returns(4)
  object.method # => 1
  object.method # => 2
  object.method # => raises exception of class Exception
  object.method # => 4

Yields on Consecutive Invocations

Allow multiple calls to yields on single expectation to allow yield parameters to be specified for consecutive invocations.

  object = mock()
  object.stubs(:method).yields(1, 2).then.yields(3)
  object.method { |*values| p values } # => [1, 2]
  object.method { |*values| p values } # => [3]

Multiple Yields on Single Invocation

Added Expectation#multiple_yields to allow a mocked or stubbed method to yield multiple times for a single invocation.

  object = mock()
  object.stubs(:method).multiple_yields([1, 2], [3])
  object.method { |*values| p values } # => [1, 2] # => [3]

Invocation Dispatch

Expectations were already being matched in reverse order i.e. the most recently defined one was being found first. This is still the case, but we now stop matching an expectation when its maximum number of expected invocations is reached. c.f. JMock v1. A stub will never stop matching by default. Hopefully this means we can soon get rid of the need to pass a Proc to Expectation#returns.

  object = mock()
  object.stubs(:method).returns(2)
  object.expects(:method).once.returns(1)
  object.method # => 1
  object.method # => 2
  object.method # => 2
  # no verification error raised

This should still work…

  Time.stubs(:now).returns(Time.parse('Mon Jan 01 00:00:00 UTC 2007'))
  Time.now # => Mon Jan 01 00:00:00 UTC 2007
  Time.stubs(:now).returns(Time.parse('Thu Feb 01 00:00:00 UTC 2007'))
  Time.now # => Thu Feb 01 00:00:00 UTC 2007

Acknowledgements

Thanks to David Chelimsky, Dan North, Jay Fields, Kevin Clark, Frederick Cheung, James Moore, Brian Helmkamp, Ben Griffiths, Chris Roos & Paul Battley for their input. Apologies to anybody I forgot to mention.

Posted in  | Tags , , , , ,  | 1 comment

Honourable Mention for Mocha

Posted by James Mead Thu, 10 May 2007 15:15:00 GMT

I just downloaded an updated version of the Pragmatic Bookshelf’s Agile Web Development with Rails book. Maybe it’s been there a while, but I just noticed Mocha is mentioned at the end of the chapter on testing. :-)

Tags , , , , ,  | no comments

Preview of Latest Mocha Changes

Posted by James Mead Thu, 12 Apr 2007 11:57:00 GMT

I’ve finally managed to find some time to do some serious work on Mocha. Here are some code snippets showing the new functionality available in trunk (revision 128). I don’t don’t know how many people out there are using trunk, but it would be great to get some feedback on these changes before I make a new release. In particular I’d like to know whether…

  • I’ve broken anybody’s tests.
  • Anybody has a legitimate use for parameter_block in Expectation#with that they don’t think will be handled by a suitable parameter matcher i.e. using the current behaviour where the block is passed the parameters and the result of the block determines whether the expectation matches. I’m planning on deprecating this soon.
  • Anybody has a legitimate use for passing in an instance of Proc to Expectation#returns i.e. using the current behaviour where the Proc gets executed to generate a return value. I’m planning on deprecating this soon as well.

I’ve typed this up in a bit of a rush (about to go on holiday for a few days), so apologies if there are any mistakes.

Parameter Matchers

I’ve added a few Hamcrest-style parameter matchers which are designed to be used inside Expectation#with. The following matchers are currently available: has_key(), has_value() & has_entry(). More to follow soon. The idea is eventually to get rid of the nasty parameter_block option on Expectation#with.

  object = mock()
  object.expects(:method).with(has_key('key_1'))
  object.method('key_1' => 1, 'key_2' => 2)
  # no verification error raised

  object = mock()
  object.expects(:method).with(has_key('key_1'))
  object.method('key_2' => 2)
  # verification error raised, because method was not called with Hash containing key: 'key_1'

Values Returned and Exceptions Raised on Consecutive Invocations

Allow multiple calls to Expectation#returns and Expectation#raises to build up a sequence of responses to invocations on the mock. Added syntactic sugar method Expectation#then to allow more readable expectations.

  object = mock()
  object.stubs(:method).returns(1, 2).then.raises(Exception).then.returns(4)
  object.method # => 1
  object.method # => 2
  object.method # => raises exception of class Exception
  object.method # => 4

Yields on Consecutive Invocations

Allow multiple calls to yields on single expectation to allow yield parameters to be specified for consecutive invocations.

  object = mock()
  object.stubs(:method).yields(1, 2).then.yields(3)
  object.method { |*values| p values } # => [1, 2]
  object.method { |*values| p values } # => [3]

Multiple Yields on Single Invocation

Added Expectation#multiple_yields to allow a mocked or stubbed method to yield multiple times for a single invocation.

  object = mock()
  object.stubs(:method).multiple_yields([1, 2], [3])
  object.method { |*values| p values } # => [1, 2] # => [3]

Invocation Dispatch

Expectations were already being matched in reverse order i.e. the most recently defined one was being found first. This is still the case, but we now stop matching an expectation when its maximum number of expected invocations is reached. c.f. JMock v1. A stub will never stop matching by default. Hopefully this means we can soon get rid of the need to pass a Proc to Expectation#returns.

  object = mock()
  object.stubs(:method).returns(2)
  object.expects(:method).once.returns(1)
  object.method # => 1
  object.method # => 2
  object.method # => 2
  # no verification error raised

This should still work…

  Time.stubs(:now).returns(Time.parse('Mon Jan 01 00:00:00 UTC 2007'))
  Time.now # => Mon Jan 01 00:00:00 UTC 2007
  Time.stubs(:now).returns(Time.parse('Thu Feb 01 00:00:00 UTC 2007'))
  Time.now # => Thu Feb 01 00:00:00 UTC 2007

Acknowledgements

Thanks to David Chelimsky, Dan North, Jay Fields, Kevin Clark, Frederick Cheung, James Moore, Brian Helmkamp, Ben Griffiths, Chris Roos & Paul Battley for their input. Apologies to anybody I forgot to mention.

Tags , , , , , , ,  | 4 comments

Mocha 0.4 released

Posted by James Mead Mon, 22 Jan 2007 12:28:00 GMT

So I finally got round to releasing a new version of Mocha. Much of the functionality has been available for some time if you’ve been using the Rails plugin based on subversion HEAD, but now you can get it in all in a gem (or other package). The most recent changes centre around allowing mocking of Object instance methods.

Release notes…

  • Allow naming of mocks (patch from Chris Roos).
  • Specify multiple return values for consecutive calls.
  • Improved consistency of expectation error messages.
  • Allow mocking of Object instance methods e.g. kind_of?, type.
  • Provide aliased versions of #expects and #stubs to allow mocking of these methods.
  • Added at_least, at_most, at_most_once methods to expectation.
  • Allow expects and stubs to take a hash of method and return values.
  • Eliminate warning: “instance variable @yield not initialized” (patch from Xavier Shay).
  • Restore instance methods on partial mocks (patch from Chris Roos).
  • Allow stubbing of a method with non-word chars in its name (patch from Paul Battley).
  • Removed coupling to Test::Unit.
  • Allow specified exception instance to be raised (patch from Chris Roos).
  • Make mock object_id appear in hex like normal Ruby inspect (patch from Paul Battley).
  • Fix path to object.rb in rdoc rake task (patch from Tomas Pospisek).
  • Reverse order in which expectations are matched, so that last expectation is matched first. This allows e.g. a call to #stubs to be effectively overridden by a call to #expects (patch from Tobias Lutke).
  • Stubba & SmartTestCase modules incorporated into Mocha module so only need to require ‘mocha’ – no longer need to require ‘stubba’.
  • AutoMocha removed.

Thanks to all who contributed.

Enjoy :-)

Posted in  | Tags , , , , ,  | no comments

Stub Queries and Expect Commands

Posted by James Mead Sat, 23 Dec 2006 20:02:00 GMT

In Making a Mockery of ActiveRecord, a single test has multiple expectations set up for accessor methods…

  specify "should create EmailMessages and Subscriptions when include_subscribers is true" do
    @message.include_subscribers = true
    @message.should_receive(:lists).and_return([@list])
    @message.should_receive(:campaign).twice.and_return(@campaign)
    @list.should_receive(:people).and_return([@person])
    @person.should_receive(:campaigns).and_return([])

    @person.should_receive(:subscriptions).and_return(@subscriptions)
    @subscriptions.should_receive(:create).and_return(nil)
    @campaign.should_receive(:people).and_return([])

    message.generate # I think this line is missing from the original test
    @message.should_have(1).email_messages
  end

I’ve found Nat Pryce’s rule of thumb – stub queries and expect commands – to be very useful in reducing the brittleness of tests (see Yoga for Your Unit Tests).

A query in this context is a method which does not change the state of the object on which it is called. The accessor methods definitely fall into this category, so I would stub them, not set expectations for them. The one command method in the above example which merits an expectation is the call to create on @subscriptions.

However, I prefer to use in-memory ActiveRecord objects instead of mocks or stubs wherever possible. So I’d write something more like this (using Test::Unit and Mocha)...

  def test_should_create_email_messages_and_subscriptions_when_include_subscribers_is_true
    subscriptions = mock()
    person = Person.new(:campaigns => [])
    person.stubs(:subscriptions).returns(subscriptions)
    list = List.new(:people => [person])
    campaign = Campaign.new(:people => [])
    message = Message.new(:lists => [list], :campaign => campaign)
    message.include_subscribers = true

    subscriptions.expects(:create)

    message.generate
    assert_equal 1, message.email_messages
  end

I haven’t actually run this – so it’s quite likely there are errors in it, but you should get the general idea. The person.stubs line is necessary to avoid the type checking that Luke Redpath mentions in his comment.

More thoughts here.

Tags , , , , , ,  | 1 comment

Fixtures, mock objects or in-memory ActiveRecord objects?

Posted by James Mead Sat, 23 Dec 2006 20:00:00 GMT

Wilson Bilkovich has posted an article about mocking ActiveRecord objects.

A new method mock_model is defined that builds a mock object which will respond the same way as a real ActiveRecord object. As I understand it, this means he can replace…

  @campaign = mock("campaign")
  @campaign.stub!(:is_a?).and_return(true)
  @campaign.stub!(:new_record?).and_return(false)
  @campaign.stub!(:id).and_return(rand(1000))

with…

  mock_model :campaign

Although I agree with him that using fixtures is not a good idea, why not use a real ActiveRecord object…

  @campaign = Campaign.new

Sometimes due to the way ActiveRecord couples your models to the database, it becomes essential to have a model in the database and not just in memory. In which case why not just do this…

  @campaign = Campaign.create!

I’ve written up a couple more thoughts here and here.

Tags , , , , , ,  | 1 comment

Mocha Adoption

Posted by James Mead Fri, 17 Nov 2006 05:52:00 GMT

It’s great to see that so many people are finding Mocha useful. But it’s particularly rewarding to see people recommend it to others…

Tags , , , ,  | 2 comments

Older posts: 1 2 3