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

David Chelimsky dchelimsky at gmail.com
Fri Jul 25 09:40:53 EDT 2008


> 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.

> 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?

> Again, I realize this is horrible, but it should still work, no?


More information about the rspec-users mailing list