Problems with mock assigned to a constant

Hi all,

Initially I thought this was a bug in the built-in mocking framework(and
it
still may be), but I better hash it out on the mailing list before I
file/reopen the ticket:

http://rspec.lighthouseapp.com/projects/5645/tickets/478-mocks-on-constants#ticket-478-6

I thought my example illustrated my problem, but obviously I was passing
the
wrong arguments to the mock. I revised my example to more clearly state
my
problem:

This is a snip of a some code from a library I’m writing. When I run
this
spec I get the following:

spec migration_spec.rb

.F

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

2 examples, 1 failure


I want to mock out MyModel completely because it’s an ActiveRecord
object.
As suggested, if I define the MyModel class, this whole spec will pass.
But, I don’t think I should need to do that, should I? So it seems to
only
occur when I assign the mock to a constant.

So, even though MyModel.count is stubbed in the before block, the mock
reports an unexpected message. Note however the first example passes.

I had difficulty figuring out the cause of this, so I just gave FlexMock
a
shot (since it’s so easy to swap out). FlexMock passed both
examples(not
that, that alone signifies a bug in rSpec).

So, my question: Is this a bug in the built in mocking framework, or am
I
doing it wrong? :slight_smile:

-Matt

On Jul 24, 2008, at 11:49 PM, Matt L. wrote:

more clearly state my problem:
Spec::Mocks::MockExpectationError in 'Migration should find the

I want to mock out MyModel completely because it’s an ActiveRecord
object. As suggested, if I define the MyModel class, this whole
spec will pass. But, I don’t think I should need to do that, should
I? So it seems to only occur when I assign the mock to a constant.

So, even though MyModel.count is stubbed in the before block, the
mock reports an unexpected message. Note however the first example
passes.

The first example passes because “should_receive” acts as a stub, too.

The second example fails, because in the second example there is no
stub for the count method. I’d suggest adding this line to
before(:each) (or to the start of each test case):

MyModel.stub!(:count).and_return 0

Scott

Scott,

Thanks, your solution does work, although I’m not sure I like it. I
like to stub out behavior in my before block but also use mock
expectations to verify behavior in my specs. Similar to what Dave
explains here:

http://blog.davidchelimsky.net/2006/11/9/tutorial-rspec-stubs-and-mocks

I defined the stubs in the before block:

MyModel = mock(‘MyModel Class’, :count => 1, :find => [@record])

So, I’m assuming by adding the expectation in the first spec, I’m
wiping out the stubs defined in the before block? If that is the case
why does the first spec not fail because of MyModel.find ?

Also, FlexMock does not seem to behave this way(not that they need to
behave the same), but I would question whether this behavior is
intentional?

-Matt

On Thu, Jul 24, 2008 at 11:00 PM, Scott T.

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)

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?

-Matt

On Jul 25, 2008, at 12:32 AM, Matt L. 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 =

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

Hope this helps,

Scott

On Jul 25, 2008, at 12:47 AM, Scott T. wrote:

was the same as:
MyModel.instance_eval do
end
stubs_and_options+ lets you assign options and stub values
method foo faked out.
Scott


rspec-users mailing list
[email protected]
http://rubyforge.org/mailman/listinfo/rspec-users

Oops. Guess I signed that one twice. I’ll make up for it by not
signing this one at all.

On Jul 25, 2008, at 12:21 AM, Matt L. wrote:

I defined the stubs in the before block:

MyModel = mock(‘MyModel Class’, :count => 1, :find => [@record])

Ah - well, I missed this part. This make much more sense.

Btw, aren’t you seeing warnings every time you run your specs?

Redefining the constant for your test, is, IMHO, the most ugly
solution you can take (and plus, it’ll break in many circumstances -
for instance, it probably wont’ play well with rails loading
schemes). One way around this is by using dependency injection - I
would highly recommend you use this technique. DON’T use the
constant technique, unless you really know what your doing.

Unfortunately there are times when DI doesn’t work (especially in the
rails world) - in those cases, you really have no other option besides
stubbing the class methods directly.

wiping out the stubs defined in the before block? If that is the case
why does the first spec not fail because of MyModel.find ?

Well - what is happening between each test case? Are the classes
(defined elsewhere) being reloaded each time? Are you getting a
warning when you redefine MyModel? Do you understand how rails is
(re)loading this stuff?

Scott

On Thu, Jul 24, 2008 at 11:38 PM, Scott T.
[email protected] wrote:

http://blog.davidchelimsky.net/2006/11/9/tutorial-rspec-stubs-and-mocks

I defined the stubs in the before block:

MyModel = mock(‘MyModel Class’, :count => 1, :find => [@record])

Ah - well, I missed this part. This make much more sense.

Btw, aren’t you seeing warnings every time you run your specs?

I’m not sure what code you’re looking at, but in the gist paste I
posted for this thread, I’m only defining the constant once(
before(:all) ). If you’re looking at the lighthouse code, I was
undefining the constants in the after block. So, no, I’m not seeing
warnings.

Redefining the constant for your test, is, IMHO, the most ugly solution you
can take (and plus, it’ll break in many circumstances - for instance, it
probably wont’ play well with rails loading schemes). One way around this
is by using dependency injection - I would highly recommend you use this
technique. DON’T use the constant technique, unless you really know what
your doing.

Like I said I’m not redefining the constants. Thanks for the insight,
I’ll research DI.

All my code for the example was posted in gist ( you can download it
if you’d like ). I’m not using Rails, I’m writing a library that uses
ActiveRecord. But, all that is mocked out. The example in gist fully
illustrates the problem in my actual library. The output that I
posted in the first post is exactly what you get if you run the code
pasted in gist:

http://gist.github.com/2372

Sorry, if that was confusing, I shouldn’t have even mentioned the
lighthouse ticket.

On Thu, Jul 24, 2008 at 11:47 PM, Scott T.
[email protected] wrote:

was the same as:
MyModel.instance_eval do
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).

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?

On Jul 25, 2008, at 1:05 AM, Matt L. wrote:

explains here:
Btw, aren’t you seeing warnings every time you run your specs?
can take (and plus, it’ll break in many circumstances - for
Like I said I’m not redefining the constants. Thanks for the insight,

why does the first spec not fail because of MyModel.find ?
if you’d like ). I’m not using Rails, I’m writing a library that uses
ActiveRecord. But, all that is mocked out. The example in gist fully
illustrates the problem in my actual library. The output that I
posted in the first post is exactly what you get if you run the code
pasted in gist:

http://gist.github.com/2372

Sorry, if that was confusing, I shouldn’t have even mentioned the
lighthouse ticket.

No - it was my own fault. I didn’t take the time to read your post
fully.

Anyway - DI is surely the way to go. And generally you’ll want to
stay away from before(:all) when you can.

Best,

Scott

On Fri, Jul 25, 2008 at 12:25 AM, Scott T.
[email protected] wrote:

Object.send :remove_const, :MyModel
(or:)

If those example aren’t doing the exact same thing I guess I’m a

But, I’m starting to think they are not. I haven’t looked at the
stubs. A stub is just a faked out method - it can exist on a mock object
A real object will work as usual. (A null object mock is a special type
That’s right - the hash to the mock method is a shorthand. So these two are


rspec-users mailing list
[email protected]
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

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)

On Jul 25, 2008, at 1:15 AM, Matt L. wrote:

MyModel = mock(‘MyModel Class’, :count => 1)
and here’s the second form:
class << self
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)

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

On Fri, Jul 25, 2008 at 12:34 AM, Matt L. [email protected] wrote:

I suppose the way I’m defining the stubs, differs from what Dave is
Nope. Not even close. Here’s an equivalent of the first form:
end

MyModel.stub!(:count).and_return(1)

Yeah - so here are two related, but not equivalent ideas: mock objects,
which it doesn’t understand (i.e. one which hasn’t been explicitly
doing?

This:
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 (this is my first time
checking out gist - wow!).

HTH,
David

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 7:57 AM, David C. [email protected]
wrote:

end
Scott
MyModel = mock(‘MyModel Class’)

message
complaining. If +stubs_and_options+ isn’t stubbing, then what is it
my_mock = mock(“a mock”, {:foo => :bar})

Spec::Mocks::MockExpectationError in ‘Migration should find the records’
spec_helper, of course you need the flexmock gem)
example, it’s not expecting it (which is exactly what it’s telling

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

def count
end
stubs on the mock object. I’m using it as a shortcut for this:

What is the difference between a mock object and a fake object? A mock
Ok, I get what you saying, but as I understand it I am explicitly
AND:

And like I said earlier, this code passes both examples with FlexMock(
expression of this?). After the first example is executed, that stub
to ignore unexpected messages, however the stub on find might still
state across examples. The way I usually go about something like this
Or you could just look at them on line!
David
On Fri, Jul 25, 2008 at 8:30 AM, Matt L. [email protected] 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?

On Fri, Jul 25, 2008 at 8:44 AM, Matt L. [email protected] wrote:

[email protected] wrote:

was the same as:
MyModel.instance_eval do
end
By passing {:count => 1} to +stubs_and_options+ I should have defined
whatever it was before - say, a class)

Anything else is treated as a stub value.

.F

examples are run (perhaps before(:any) would be a more clear
Another option is to set :null_object => true. That will tell the mock
are run only once, and can cause a lot of confusion because they leak

HTH,

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’

On Fri, Jul 25, 2008 at 8:49 AM, David C. [email protected]
wrote:

On Thu, Jul 24, 2008 at 11:47 PM, Scott T.

def count; 1; end

redefining a class method (the constant isn’t changing - it’s remaining

at the same time. The only option available is :null_object.
MyRealObject.stub!(:foo) is a real object with the method foo faked out.

my_mock.stub!(:foo).and_return(:bar)
http://rubyforge.org/mailman/listinfo/rspec-users

spec migration_spec.rb


before(:all) block, which is only executed once, before all the

I don’t use before(:all) blocks this way for exactly this reason. They
separate commits, so grab the repo to see both

One more bad implementation if you have time:
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’

I actually just did that myself and they both pass separately - so
there is some state-leaking problem. I don’t really have the time to
investigate this now, but this is beginning to feel a lot like
“doctor, it hurts when I bang my head against the wall like this …”

On Fri, Jul 25, 2008 at 8:40 AM, David C. [email protected]
wrote:

def count
end
stubs on the mock object. I’m using it as a shortcut for this:

What is the difference between a mock object and a fake object? A mock
Ok, I get what you saying, but as I understand it I am explicitly
AND:

And like I said earlier, this code passes both examples with FlexMock(
expression of this?). After the first example is executed, that stub
to ignore unexpected messages, however the stub on find might still
state across examples. The way I usually go about something like this
Or you could just look at them on line!
David
I don’t really have much - packing for a week’s holiday.
Ok, thanks anyway. Enjoy your holiday. :slight_smile:

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.

Fair enough. Thanks.

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs