Forum: RSpec (no subject)

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Marcus R. (Guest)
on 2009-02-27 18:37
(Received via mailing list)
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
David C. (Guest)
on 2009-02-27 18:50
(Received via mailing list)
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 :)

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
Pat M. (Guest)
on 2009-02-27 19:11
(Received via mailing list)
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
Matt W. (Guest)
on 2009-02-27 19:12
(Received via mailing list)
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
Marcus R. (Guest)
on 2009-02-27 19:16
(Received via mailing list)
>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
Matt W. (Guest)
on 2009-02-27 19:27
(Received via mailing list)
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
Marcus R. (Guest)
on 2009-02-27 19:33
(Received via mailing list)
>This is not an rspec bug :)

I thought not :-)

>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
Matt W. (Guest)
on 2009-02-27 19:35
(Received via mailing list)
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
Marcus R. (Guest)
on 2009-02-27 19:47
(Received via mailing list)
>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
Pat M. (Guest)
on 2009-02-27 19:49
(Received via mailing list)
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
Pat M. (Guest)
on 2009-02-27 20:13
(Received via mailing list)
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
Marcus R. (Guest)
on 2009-02-27 20:18
(Received via mailing list)
>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
This topic is locked and can not be replied to.