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

Matt Lins mattlins at gmail.com
Fri Jul 25 09:30:20 EDT 2008


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:

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.

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

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
>>
> _______________________________________________
> rspec-users mailing list
> rspec-users at rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-users
>


More information about the rspec-users mailing list