Ruby Mocha: Is There an Equivalent to Rspec-Mocks' #And_Call_Original

Expect mock result to receive method

You broke MyPolicy.new.

Your wrapper for new does not return a new MyPolicy object. It returns the result of expect(method.call(*args)).to receive(:show?).and_call_original which is a MessageExpectation.

Instead, you can ensure the new object is returned with tap.

      # This is an allow. It's not a test, it's scaffolding for the test.
allow(MyPolicy).to receive(:new)
.and_wrap_original do |method, *args|
method.call(*args).tap do |obj|
expect(obj).to receive(:show?).and_call_original
end
end

Or do it the old fashioned way.

      allow(MyPolicy).to receive(:new)
.and_wrap_original do |method, *args|
obj = method.call(*args)
expect(obj).to receive(:show?).and_call_original
obj
end

It is often simpler to separate the two steps. Mock MyPolicy.new to return a particular object and then expect the call to show? on that object.

let(:policy) do
# This calls the real MyPolicy.new because policy is referenced
# when setting up the MyPolicy.new mock.
MyPolicy.new
end

before do
allow(MyPolicy).to receive(:new).and_return(policy)
end

it 'shows' do
expect(policy).to receive(:show?).and_call_original
MyPolicy.new.show?
end

This does mean MyPolicy.new always returns the same object. That's an advantage for testing, but might break something. This is more flexible since it separates the scaffolding from what's being tested. The scaffolding can be reused.

RSpec.describe SomeClass do
let(:policy) {
MyPolicy.new
}
let(:thing) {
described_class.new
}

shared_context 'mocked MyPolicy.new' do
before do
allow(MyPolicy).to receive(:new).and_return(policy)
end
end

describe '#some_method' do
include_context 'mocked new'

it 'shows a policy' do
expect(policy).to receive(:show?).and_call_original

thing.some_method
end
end

describe '#other_method' do
include_context 'mocked MyPolicy.new'

it 'checks its policy' do
expect(policy).to receive(:check)

thing.other_method
end
end
end

Finally, inaccessible constructor calls are a headache both for testing, and they're inflexible. It's a default which cannot be overridden.

class SomeClass
def some_method
MyPolicy.new.show?
end
end

Turn it into an accessor with a default.

class SomeClass
attr_writer :policy

def policy
@policy ||= MyPolicy.new
end

def some_method
policy.show?
end
end

Now it can be accessed in the test or anywhere else.

RSpec.describe SomeClass do
let(:thing) {
described_class.new
}

describe '#some_method' do
it 'shows its policy' do
expect(thing.policy).to receive(:show?).and_call_original
thing.some_method
end
end
end

This is the most robust option.

how to reset expectations on a mocked class method?

I could not find anywhere in the documentation that this is how you should do it, and past behaviors goes to show that this solution might also change in the future, but apparently this is how you can currently do it:

RSpec::Mocks.space.proxy_for(your_object).reset

I would follow @BroiSatse's remark, though, and think about re-designing the tests, aiming to move the expectation from the before block. The before block is meant for setup, as you say, and the setup is a very weird place to put expectations.

I'm not sure how you came to this design, but I can suggest two possible alternatives:

  • If the test is trivial, and will work anyway, you should create one test with this explicit expectation, while stubbing it for the other tests:

    before(:each) do
    allow(File).to receive(:exist?).with("dummy.yaml").and_return (true)
    end

    it "asks if file exists" do
    expect(File).to receive(:exist?).with("dummy.yaml").and_return (true)
    # do the test...
    end
  • If the expectation should run for every test, since what changes in each scenario is the context, you should consider using shared examples:

    shared_examples "looking for dummy.yaml" do 
    it "asks if file exists" do
    expect(File).to receive(:exist?).with("dummy.yaml").and_return (true)
    # do the test...
    end
    end

    it_behaves_like "looking for dummy.yaml" do
    let(:scenario) { "something which sets the context"}
    end

You might also want to ask myron if there is a more recommended/documented solution to reset mocked objects...

rspec: How to stub an instance method called by constructor?

Here's the commit which adds the feature to rspec - This was on May 25 2008. With this you can do

A.any_instance.stub(do_something: 23)

However, the latest gem version of rspec (1.1.11, October 2008) doesn't have this patch in it.

This ticket states that they yanked it out for maintenance reasons, and an alternative solution hasn't yet been provided.

Doesn't look like you can do it at this point. You'll have to hack the class manually using alias_method or somesuch.

How to assert certain method is called with Ruby minitest framework?

With minitest you use expect method to set the expectation for a method to be called on a mock object like so

obj = MiniTest::Mock.new
obj.expect :right

If you want to set expectation with parameters and return values then:

obj.expect :right, return_value, parameters

And for the concrete object like so:

obj = SomeClass.new
assert_send([obj, :right, *parameters])


Related Topics



Leave a reply



Submit