#68 ✓resolved
James Mead

during block for mocha

Reported by James Mead | July 27th, 2010 @ 09:37 AM

James,
Thanks for Mocha!

There are times when I want to stub something just briefly. Time.now, for instance; in rspecs, or even in cucumber. So I monkey patch mocha with the following:

module Mocha # :nodoc:

  # Methods on expectations returned from Mock#expects, Mock#stubs, Object#expects and Object#stubs.
  class Expectation

    def during(&block)
      yield

      Mocha::Mockery.instance.stubba.stubba_methods.each do |meth|
        if meth.stubbee == @mock.instance_variable_get('@name').instance_variable_get('@object') && meth.method == @method_matcher.expected_method_name.to_s
          meth.unstub
        end
      end
    end

  end
  
end

That way I can do:

Time.stubs(:now).returns(my_time).during { my block of code }

This seems to work for objects and classes, but not for any_instance (it fails to revert to the previous behavior).

It'd be nice to have it included in mocha, proper. I know I'm not the only one who wants this kind of thing.

Thank again!

Kurt

http://www.CircleW.org/kurt/

Comments and changes to this ticket

  • Ken Collins

    Ken Collins July 27th, 2010 @ 02:39 PM

    Interesting, I might use this. It does seem very useful when you need discreet stubs during a long running test typically in integration/functional land. Possibly a bad pattern in unit land, not sure and moot since you cant make people write good tests with good tools :)

    All in all, I like this a lot. My only technical feedback would be to ask if the &block var is needed? Isn't it better practice to only use that var if it is really needed to yield (no pun) better performance.

  • kwerle

    kwerle July 27th, 2010 @ 05:45 PM

    I use this in rspec tests, mostly. It's also useful if you want to have part of a test say that "now" is 2 weeks ago, then 1 week ago, then really now.

    As for the &block, I didn't know you could yield without one! In either case, it seems like the only real use for during is with a block -- may as well make it explicit?

    Thanks to James for putting this where I should have in the first place!

  • Ken Collins

    Ken Collins July 27th, 2010 @ 07:09 PM

    Yea, totally can without.

    def blocked
      puts 'before'
      yield
      puts 'after'
    end
    
    blocked do
      puts 'doing'
    end
    
    # >> before
    # >> doing
    # >> after
    
  • Josh Clayton

    Josh Clayton July 28th, 2010 @ 01:46 PM

    Outside of stubbing Time, are there any decent use cases? I can't think of any. If this is what you're going for, why not use a library built specifically for this? Timecop is awesome and will do exactly what you're discussing:

    Timecop.freeze("10/1/2009".to_date) do
      Time.now # Thu Oct 01 00:00:00 -0400 2009
      Time.now.advance(:days => 3) # Sun Oct 04 00:00:00 -0400 2009
      Date.today # Thu, 01 Oct 2009
    end
    
    Time.now # Wed Jul 28 08:40:31 -0400 2010
    

    If you're using Cucumber, you can add something like this:

    # in features/step_definitions/time_steps.rb
    
    Given /^today is "([^\"]*)"$/ do |date_time|
      Timecop.freeze Time.parse(date_time)
    end
    
    After { Timecop.return }
    

    What this allows you to do is declare a step that essentially freezes time; in the After block, Timecop removes all time stubs so you're properly reset to the correct time after each scenario.

    Hopefully this helps!

  • kwerle

    kwerle July 28th, 2010 @ 04:42 PM

    Right - so the only reason (IMHO) that timecop exists is because these few lines of code are missing from Mocha. Why perpetuate the need for yet another gem?

    Other examples: the mind boggles. Any Ruby System level class springs to mind. You want File.open("file that should never be missing", r) to fail? Here you go. You want Tempfile.new to return a specific file at a specific path so you can check the contents? Sure. You want a singleton that's used to access an external service to act like it is missing, but only briefly? Why not.

  • Josh Clayton

    Josh Clayton July 29th, 2010 @ 04:57 AM

    Those are great examples of stubbing, but why would you only need a stub for part of that test? Are you familiar with Mocha's then? This can be used to achieve the same thing; return something the first time File.open is called, then return another result, just like it sounds and exactly what you're describing here. For testing external services, what about Fakeweb? Sham Rack? The possibilities are endless! Making Mocha the "end all be all" of stubbing/mocking isn't the solution, especially when these solutions exist in other gems.

    Also, I'd suggest looking at Timecop's source - the library is definitely not "a few lines of missing code". Another point I'd like to bring up is that if a decent developer is using Mocha for mocking/stubbing, he's probably writing integration tests (likely with Cucumber). Many (if not most) apps (or gems) deal with time, so Cucumber scenarios become easier to write with Timecop; it's almost a given that it's included in every app I develop.

    Anyways, I don't want to get into an argument about how to test or anything; I'm just explaining that what it sounds like you're trying to accomplish outside of Time already exists in Mocha (then) and Timecop exists for times/dates. If the solutions to these issues already exist, why add more code to a library?

  • kwerle

    kwerle July 29th, 2010 @ 05:35 AM

    during seems like a reasonable and simple solution to the general problem that is:
    "it is hard to unstub"

    then is a fine solution if you're not worried about "then(be normal)", which seems hard to do with mocha.

    Timecop could be huge - I don't know, and I really don't care. Though I don't see why it should be huge - given a couple of trivial changes in Mocha. In fact it ought to be tiny. Seriously - it should be trivial to get mocha to do

    Something.stub(:method).returns(my_value).during { ... }
    or
    Something.stub(:method).returns(my_value)
    ... Something.unstub(:method)

    But it isn't. Which is not to say that it's a lot of code - it's just a few lines of super-obscure undocumented code.

    I'd like to fix something that I think is missing in Mocha - with just a few lines of code. In fact, I've already fixed it for myself with a monkey patch. And I know there are others (just google a little bit and you'll see - or I can provide references) who want to do the same kind of thing. What can I say - I'm a sharing kinda guy.

  • James Mead

    James Mead August 9th, 2010 @ 10:44 AM

    Thanks for all the discussion on this ticket. I think everyone has made some good points.

    I agree that it should be easier to "unstub" a method. I thought there was already a ticket for this, but it seems not. I'm going to create one [1]. I've added some questions in there that it would be great to have feedback on.

    I think it should be possible to do something like the "during" functionality with the existing Mocha::API#states [2] functionality. However, since this functionality is significantly more flexible than the "during" concept it is inevitably more verbose. I can see an argument for providing a shortcut way of doing it, but I'm pondering whether it belongs in the "core" of Mocha - I'm not sure it does. I'm thinking pretty seriously of trying to separate out a "core" part of Mocha with useful extension points.

    Whatever happens, any change will need to be accompanied by a set of acceptance tests.

    Cheers, James.

    [1] http://floehopper.lighthouseapp.com/projects/22289-mocha/tickets/69... [2] http://mocha.rubyforge.org/classes/Mocha/API.html#M000006

  • kwerle

    kwerle August 9th, 2010 @ 04:51 PM

    I agree that this is a better solution. With a solid #unstub in place, #during becomes trivial to write.

    The reason I suggested #during is specifically because the API used was obscure, and because a 'real' solution for unstub was outside the scope of work I was willing to undertake to solve my particular problem.

    Thanks for being a good shepherd!

  • James Mead

    James Mead November 12th, 2010 @ 02:41 PM

    I've added "unstub" functionality.

  • James Mead

    James Mead November 25th, 2010 @ 05:12 PM

    • State changed from “new” to “resolved”

    I'm marking this ticket as closed.

Please Sign in or create a free account to add a new ticket.

With your very own profile, you can contribute to projects, track your activity, watch tickets, receive and update tickets through your email and much more.

New-ticket Create new ticket

Create your profile

Help contribute to this project by taking a few moments to create your personal profile. Create your profile ยป

A mocking & stubbing library for Ruby.

* <a href="http://github.com/floehopper/mocha">GitHub repository</a>
* <a href="http://mocha.rubyforge.org">Documentation</a>
* <a href="http://groups.google.com/group/mocha-developer">Mailing List</a>

People watching this ticket

Referenced by

Pages