(no subject)


#1

Apologies as this feels like an FAQ, but the only answer I can find
refers to a bug in a much earlier version of rspec, and this feels like
a common thing to do, so I suspect we’re doing something stupid.

The issue seems to be that if we mock a class, that mock carries between
specs when running ‘rake spec’ - the specs pass when run individually.

In one spec, we mock an active record model:

MachineInstance = mock(“MachineInstance”)

in a spec in another file that runs later, when we access
MachineInstance as what should be a regular activerecord class, it’s
still a mock:

Spec::Mocks::MockExpectationError in ‘MachineInstance should create a
new instance given valid attributes’
Mock ‘MachineInstance’ received unexpected message :create! with
({:name=>“value for name”, :machine_image_id=>1})

We’re running rspec 1.1.12.

Thanks for any pointers.

Marcus


#2

On Fri, Feb 27, 2009 at 10:14 AM, Marcus R.
removed_email_address@domain.invalid wrote:

Apologies as this feels like an FAQ, but the only answer I can find refers
to a bug in a much earlier version of rspec, and this feels like a common
thing to do, so I suspect we’re doing something stupid.

The issue seems to be that if we mock a class, that mock carries between
specs when running ‘rake spec’ - the specs pass when run individually.

In one spec, we mock an active record model:

MachineInstance = mock(“MachineInstance”)

This is not an rspec bug :slight_smile:

This is assigning the value mock(“MachineInstance”) to the constant
MachineInstance, which an interesting way to approach stubbing a
class, but not really the idea.

Can you show us the spec this is in so we can make appropriate
recommendations?

Thanks,
David


#3

On Fri, Feb 27, 2009 at 8:14 AM, Marcus R.
removed_email_address@domain.invalid wrote:

Thanks for any pointers.

Marcus


rspec-users mailing list
removed_email_address@domain.invalid
http://rubyforge.org/mailman/listinfo/rspec-users

Unless RSpec is doing some magic under the hood that I’m not aware of,
this is expected behavior - you’re reassigning the constant value of
MachineInstance.

What you really want to do is mock/stub directly on MacineInstance.
e.g.

MachineInstance.should_receive(:create).and_return @mock_machine

Pat


#4

Unless RSpec is doing some magic under the hood that I’m not aware of,
this is expected behavior - you’re reassigning the constant value of
MachineInstance.

What you really want to do is mock/stub directly on MacineInstance. e.g.

MachineInstance.should_receive(:create).and_return @mock_machine

If we did that, would that stub/mock carry over into other spec files
run later by autospec, which is what we’re seeing.
I can see we’re changing the definition of MachineInstance in the spec
we do that in, but should that carry over to later specs?

Marcus


#5

bloody mailing list lag - three of us answered the same question!

On 27 Feb 2009, at 16:46, Matt W. wrote:

http://blog.mattwynne.net
http://www.songkick.com


rspec-users mailing list
removed_email_address@domain.invalid
http://rubyforge.org/mailman/listinfo/rspec-users

Matt W.
http://blog.mattwynne.net
http://www.songkick.com


#6

On 27 Feb 2009, at 16:14, Marcus R. wrote:

MachineInstance = mock(“MachineInstance”)

From this code above, you’re setting a constant, ‘MachineInstance’.
Ruby will keep that constant for subsequent test runs - nothing to do
with RSpec.

Assuming you want to mock an instance of your MachineInstance class,
use this in your spec:

machine_instance = mock(“MachineInstance”)

AFAIK Ruby uses the case of the first letter to determine whether
something’s a (global) constant or a (local) variable. You want the
latter, I think, so use lower case letters.

If what you want to do is mock the class itself, that’s a different
story, and we can talk about that.

Matt W.
http://blog.mattwynne.net
http://www.songkick.com


#7

This is not an rspec bug :slight_smile:

I thought not :slight_smile:

This is assigning the value mock(“MachineInstance”) to the constant
MachineInstance, which an interesting way to approach stubbing a
class, but not really the idea.

Can you show us the spec this is in so we can make appropriate recommendations?

We have a class declared in lib/ in a Rails app which we want to test
independently of the activerecord class / database. The full spec as it
stands is:

require File.expand_path(File.dirname(FILE) + ‘/…/spec_helper’)

describe “a service at level 0” do

it “should pass a test and do nothing” do

service = mock("service")
service.should_receive(:test_service)
service.should_receive(:save)

machineInstance = mock("MachineInstance")
machineInstance.should_receive(:services).and_return([service])

MachineInstance = mock("MachineInstance")
MachineInstance.should_receive(:find).and_return([machineInstance])

monitoringAlerts = MonitoringAlerts.new
monitoringAlerts.monitor

end
end

The idea is to have no dependency on the MachineInstance class in this
spec, which seems to work, but specs called later still see
MachineInstance as the mock.

Are we doing this completely the wrong way?

Thanks!

Marcus


#8

On 27 Feb 2009, at 17:08, Marcus R. wrote:

If we did that, would that stub/mock carry over into other spec
files run later by autospec, which is what we’re seeing.
I can see we’re changing the definition of MachineInstance in the
spec we do that in, but should that carry over to later specs?

That’s the way Ruby behaves, yes.

Once you assign the constant MachineInstance to point to your mock,
Rails’ magic auto-file-loading which normally happens when Ruby fires
a ‘missing constant error’ will never run, so your real class will
never get loaded.

Assigning constants in your tests is a pretty bad idea unless you know
exactly what you’re doing.

Matt W.
http://blog.mattwynne.net
http://www.songkick.com


#9

That’s the way Ruby behaves, yes.

Once you assign the constant MachineInstance to point to your mock,
Rails’ magic auto-file-loading which normally happens when Ruby fires
a ‘missing constant error’ will never run, so your real class will
never get loaded.

Assigning constants in your tests is a pretty bad idea unless you know
exactly what you’re doing.

Assuming that assigning a constant was the right thing to do, it means
there’s different behaviour when running autospec versus running the
specs individually.

I’m going to assume that we’re doing the wrong thing by assigning a
constant, which changes my question to how do we mock/stub the behaviour
of our classes without that behaviour passing from spec to spec when
running autospec.

Thanks

Marcus


#10

On Fri, Feb 27, 2009 at 9:36 AM, Marcus R.
removed_email_address@domain.invalid wrote:

I’m going to assume that we’re doing the wrong thing by assigning a constant, which changes my question to how do we mock/stub the behaviour of our classes without that behaviour passing from spec to spec when running autospec.

Take out the constant reassignment. Did you see my comments earlier
about partial mocking?

Pat


#11

No, because RSpec keeps track of partial mocks (mocks/stubs on “real”
objects) and clears them out at the end of the example run.

Thank you! This is the key, along with understanding we can just
mock/stub onto the real object, without needing to make the object a
mock object.

As soon as we just mock/stub, everything works as expected.

Thanks everyone for all the help!

Marcus


#12

On Fri, Feb 27, 2009 at 9:08 AM, Marcus R.
removed_email_address@domain.invalid wrote:

If we did that, would that stub/mock carry over into other spec files run
later by autospec, which is what we’re seeing.

No, because RSpec keeps track of partial mocks (mocks/stubs on “real”
objects) and clears them out at the end of the example run.

I can see we’re changing the definition of MachineInstance in the spec we do
that in, but should that carry over to later specs?

Yes. This is a Ruby thing, not an RSpec thing. Same as if you were to
do
MachineInstance = SomeOtherMachineInstance

where SomeOtherMachineInstance is a class that you defined. You’re
reassigning a constant. Why would RSpec know or care when you do
that?

Put another way, while RSpec knows when you create a mock object, it
has no clue when/if you assigned it to something, and it certainly
doesn’t know the previous value.

The built-in framework recognizes that your intent when partially
mocking an object is to use it within a certain, small scope (an
example). It then politely cleans up after itself. If you work
within those parameters, you’ll see the behavior that you want.

If you really want to just assign the constant (and I don’t see why
you would…mocking methods on it directly works fine), you can store
the constant’s value in a before and reset it in after:

describe “blah” do
before(:each) do
@klass = MachineInstance
MachineInstance = mock(‘mi’)
end

after(:each) do
MachineInstance = @klass
end
end

Pat