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...
endIf 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
How to Make a Post Request with Open-Uri
Is There a Way in Ruby 1.9 to Remove Invalid Byte Sequences from Strings
Rails Put Validation in a Module Mixin
Converting from Xml Name-Values into Simple Hash
Safe Navigation Operator (&.) for Nil
Problems Setting a Custom Primary Key in a Rails 4 Migration
Error: While Executing Gem ... (Zlib::Gzipfile::Error) Not in Gzip Format
Sass::Syntaxerror: File to Import Not Found or Unreadable: Compass in Production
Editing Existing Rails Migrations Is a Good Idea
What's the Best/Easiest Gui Library for Ruby
Replace Words in a String - Ruby
Ruby Metaprogramming: Dynamic Instance Variable Names
How to Get a Backtrace from a Systemstackerror: Stack Level Too Deep