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

David Chelimsky dchelimsky at gmail.com
Fri Jul 25 09:49:41 EDT 2008


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'


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


More information about the rspec-users mailing list