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

Matt Lins mattlins at gmail.com
Fri Jul 25 09:44:39 EDT 2008


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.

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


More information about the rspec-users mailing list