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

David Chelimsky dchelimsky at gmail.com
Fri Jul 25 09:15:14 EDT 2008


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
>


More information about the rspec-users mailing list