[rspec-users] Problems with mock assigned to a constant

Matt Lins mattlins at gmail.com
Fri Jul 25 10:02:42 EDT 2008


Fair enough. Thanks.

On Fri, Jul 25, 2008 at 8:53 AM, David Chelimsky <dchelimsky at gmail.com> wrote:
> On Fri, Jul 25, 2008 at 8:49 AM, David Chelimsky <dchelimsky at gmail.com> wrote:
>> On Fri, Jul 25, 2008 at 8:44 AM, Matt Lins <mattlins at gmail.com> wrote:
>>> On Fri, Jul 25, 2008 at 8:40 AM, David Chelimsky <dchelimsky at gmail.com> wrote:
>>>>> On Fri, Jul 25, 2008 at 8:15 AM, David Chelimsky <dchelimsky at gmail.com> wrote:
>>>>>> On Fri, Jul 25, 2008 at 7:57 AM, David Chelimsky <dchelimsky at gmail.com> wrote:
>>>>>>> On Fri, Jul 25, 2008 at 12:34 AM, Matt Lins <mattlins at gmail.com> wrote:
>>>>>>>> On Fri, Jul 25, 2008 at 12:25 AM, Scott Taylor
>>>>>>>> <mailing_lists at railsnewbie.com> wrote:
>>>>>>>>>
>>>>>>>>> On Jul 25, 2008, at 1:15 AM, Matt Lins wrote:
>>>>>>>>>
>>>>>>>>>> On Thu, Jul 24, 2008 at 11:47 PM, Scott Taylor
>>>>>>>>>> <mailing_lists at railsnewbie.com> wrote:
>>>>>>>>>>>
>>>>>>>>>>> On Jul 25, 2008, at 12:32 AM, Matt Lins wrote:
>>>>>>>>>>>
>>>>>>>>>>>> I suppose the way I'm defining the stubs, differs from what Dave is
>>>>>>>>>>>> doing in his example.
>>>>>>>>>>>>
>>>>>>>>>>>> I assumed that:
>>>>>>>>>>>>
>>>>>>>>>>>> MyModel = mock('MyModel Class', :count => 1)
>>>>>>>>>>>>
>>>>>>>>>>>> was the same as:
>>>>>>>>>>>>
>>>>>>>>>>>> MyModel.stub!(:count).and_return(1)
>>>>>>>>>>>
>>>>>>>>>>> Nope.  Not even close.  Here's an equivalent of the first form:
>>>>>>>>>>>
>>>>>>>>>>> Object.send :remove_const, :MyModel
>>>>>>>>>>> MyModel = <a mock object>
>>>>>>>>>>>
>>>>>>>>>>> and here's the second form:
>>>>>>>>>>>
>>>>>>>>>>> MyModel.instance_eval do
>>>>>>>>>>> def count
>>>>>>>>>>>  1
>>>>>>>>>>> end
>>>>>>>>>>> end
>>>>>>>>>>>
>>>>>>>>>>> (or:)
>>>>>>>>>>>
>>>>>>>>>>> MyModel.class_eval do
>>>>>>>>>>> class << self
>>>>>>>>>>>  def count; 1; end
>>>>>>>>>>> end
>>>>>>>>>>> end
>>>>>>>>>>>
>>>>>>>>>>> Scott
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> But the stubs are defined the same way in both occurrences, no?
>>>>>>>>>>
>>>>>>>>>> MyModel = mock('MyModel Class', :count => 1)
>>>>>>>>>>
>>>>>>>>>> By passing {:count => 1} to +stubs_and_options+ I should have defined
>>>>>>>>>> stubs on the mock object.  I'm using it as a shortcut for this:
>>>>>>>>>>
>>>>>>>>>> MyModel = mock('MyModel Class')
>>>>>>>>>> MyModel.stub!(:count).and_return(1)
>>>>>>>>>>
>>>>>>>>>> If those example aren't doing the exact same thing I guess I'm a
>>>>>>>>>> little baffled (or maybe just need to go to sleep).
>>>>>>>>>
>>>>>>>>> The first one is redefining the constant 'MyModel'.  The second one is just
>>>>>>>>> redefining a class method (the constant isn't changing - it's remaining
>>>>>>>>> whatever it was before - say, a class)
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> But, I'm starting to think they are not.  I haven't looked at the
>>>>>>>>>>>> rSpec internals to verify, other than the parameter name:
>>>>>>>>>>>>
>>>>>>>>>>>> stubs_and_options+ lets you assign options and stub values
>>>>>>>>>>>> at the same time. The only option available is :null_object.
>>>>>>>>>>>> Anything else is treated as a stub value.
>>>>>>>>>>>>
>>>>>>>>>>>> So, is this problem?
>>>>>>>>>>>
>>>>>>>>>>> Yeah - so here are two related, but not equivalent ideas: mock objects,
>>>>>>>>>>> and
>>>>>>>>>>> stubs.  A stub is just a faked out method - it can exist on a mock object
>>>>>>>>>>> (a
>>>>>>>>>>> completely fake object), or on a partial mock (i.e. a real object, with a
>>>>>>>>>>> method faked out).  mock('My mock") is a mock object,
>>>>>>>>>>> MyRealObject.stub!(:foo) is a real object with the method foo faked out.
>>>>>>>>>>>
>>>>>>>>>>> What is the difference between a mock object and a fake object?  A mock
>>>>>>>>>>> object will complain (read: raise an error) any time it receives a
>>>>>>>>>>> message
>>>>>>>>>>> which it doesn't understand (i.e. one which hasn't been explicitly
>>>>>>>>>>> stubbed).
>>>>>>>>>>> A real object will work as usual.  (A null object mock is a special type
>>>>>>>>>>> of
>>>>>>>>>>> mock - one which never complains.  For now, you shouldn't worry about
>>>>>>>>>>> it).
>>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> Ok, I get what you saying, but as I understand it I am explicitly
>>>>>>>>>> stubbing out the methods on the _mock_ object and it's still
>>>>>>>>>> complaining.  If +stubs_and_options+ isn't stubbing, then what is it
>>>>>>>>>> doing?
>>>>>>>>>
>>>>>>>>> That's right - the hash to the mock method is a shorthand.  So these two are
>>>>>>>>> equivalent:
>>>>>>>>>
>>>>>>>>> my_mock = mock('a mock')
>>>>>>>>> my_mock.stub!(:foo).and_return(:bar)
>>>>>>>>>
>>>>>>>>> AND:
>>>>>>>>>
>>>>>>>>> my_mock = mock("a mock", {:foo => :bar})
>>>>>>>>>
>>>>>>>>> Scott
>>>>>>>>>
>>>>>>>>> _______________________________________________
>>>>>>>>> rspec-users mailing list
>>>>>>>>> rspec-users at rubyforge.org
>>>>>>>>> http://rubyforge.org/mailman/listinfo/rspec-users
>>>>>>>>>
>>>>>>>>
>>>>>>>> Ok, then would you still insist that:
>>>>>>>>
>>>>>>>> This:
>>>>>>>>
>>>>>>>> http://gist.github.com/2372
>>>>>>>>
>>>>>>>> Should produce this:
>>>>>>>>
>>>>>>>> # spec migration_spec.rb
>>>>>>>> .F
>>>>>>>>
>>>>>>>> 1)
>>>>>>>> Spec::Mocks::MockExpectationError in 'Migration should find the records'
>>>>>>>> Mock 'MyModel Class' received unexpected message :count with (no args)
>>>>>>>> ./migration.rb:14:in `run'
>>>>>>>> ./migration_spec.rb:19:
>>>>>>>>
>>>>>>>> Finished in 0.009435 seconds
>>>>>>>>
>>>>>>>> --------------------
>>>>>>>>
>>>>>>>> And like I said earlier, this code passes both examples with FlexMock(
>>>>>>>> if you simply replace mock with flexmock and uncomment the code in
>>>>>>>> spec_helper, of course you need the flexmock gem)
>>>>>>>
>>>>>>> I can't speak for why it's passing in Flexmock, but I can explain why
>>>>>>> it's failing in rspec.
>>>>>>>
>>>>>>> RSpec clears out all stub methods and message expectations at the end
>>>>>>> of each example. In this case, the stub on count is defined in a
>>>>>>> before(:all) block, which is only executed once, before all the
>>>>>>> examples are run (perhaps before(:any) would be a more clear
>>>>>>> expression of this?). After the first example is executed, that stub
>>>>>>> goes away. So when the mock receives the :count message in the second
>>>>>>> example, it's not expecting it (which is exactly what it's telling
>>>>>>> you). If you run the second example by itself (spec migration_spec.rb
>>>>>>> -e "should find the records") it will pass.
>>>>>>>
>>>>>>> You can solve the immediate problem by removing the stubs from the
>>>>>>> initial declaration of the MyModel constant and moving them to a
>>>>>>> before(:each) block so they get set before each example.
>>>>>>>
>>>>>>> Another option is to set :null_object => true. That will tell the mock
>>>>>>> to ignore unexpected messages, however the stub on find might still
>>>>>>> need to move to before(:each) because it returns something.
>>>>>>>
>>>>>>> Also - this code creates instance variables that get used across
>>>>>>> examples. If something happens in the first example to change the
>>>>>>> state of @record, you're going to get the same object in the second
>>>>>>> example and it becomes a challenge to understand what's happening when
>>>>>>> there are failures in the second example.
>>>>>>>
>>>>>>> I don't use before(:all) blocks this way for exactly this reason. They
>>>>>>> are run only once, and can cause a lot of confusion because they leak
>>>>>>> state across examples. The way I usually go about something like this
>>>>>>> is to create a simple empty class:
>>>>>>>
>>>>>>> class MyModel; end
>>>>>>>
>>>>>>> And then set expectations on it before(:each) example.
>>>>>>>
>>>>>>> You can get the gist of what I'm talking about here:
>>>>>>> http://gist.github.com/2438 - I've got two different approaches in two
>>>>>>> separate commits, so grab the repo to see both
>>>>>>
>>>>>> Or you *could* just look at them on line!
>>>>>>
>>>>>> https://gist.github.com/2438/040f26916032ad864ba51d0d733e16056c77be42
>>>>>> https://gist.github.com/2438/0ee4fcaebbafdbdab77dffd5228a9aae92f17191
>>>>>>
>>>>>>
>>>>>>
>>>>>>> (this is my first time
>>>>>>> checking out gist - wow!).
>>>>>>>
>>>>>>> HTH,
>>>>>>> David
>>>>
>>>> On Fri, Jul 25, 2008 at 8:30 AM, Matt Lins <mattlins at gmail.com> wrote:
>>>>> Yes, gist is great!
>>>>>
>>>>> Thank you very much for taking the time to look at this.  I like your
>>>>> suggestions very much and will use them.  At this point I'm just
>>>>> messing around, but I don't understand why this doesn't work.
>>>>>
>>>>> One more bad implementation if you have time:
>>>>
>>>> I don't really have much - packing for a week's holiday.
>>>
>>> Ok, thanks anyway.  Enjoy your holiday. :)
>>>
>>>>
>>>>> http://gist.github.com/2372
>>>>>
>>>>> I'm removing the constant after each spec runs and redefining it
>>>>> before each runs.  The second spec still doesn't pass though, even
>>>>> though the constant is defined before each spec.
>>>>
>>>> Is is the same failure?
>>>
>>> Yes.
>>
>> What about when you run them each individually?
>>
>> spec migration_spec.rb - e 'should get a count of the records'
>> spec migration_spec.rb - e 'should find the records'
>
> I actually just did that myself and they both pass separately - so
> there is some state-leaking problem. I don't really have the time to
> investigate this now, but this is beginning to feel a lot like
> "doctor, it hurts when I bang my head against the wall like this ...."
>
>>
>>
>>>
>>>>
>>>>> Again, I realize this is horrible, but it should still work, no?
>>>> _______________________________________________
>>>> rspec-users mailing list
>>>> rspec-users at rubyforge.org
>>>> http://rubyforge.org/mailman/listinfo/rspec-users
>>>>
>>> _______________________________________________
>>> rspec-users mailing list
>>> rspec-users at rubyforge.org
>>> http://rubyforge.org/mailman/listinfo/rspec-users
>>>
>>
> _______________________________________________
> rspec-users mailing list
> rspec-users at rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-users
>


More information about the rspec-users mailing list