10

Sorry if this is plain simple. i am new to ruby as well as rspec and it seems rspec is a very 'obscure' world (esp when coming from a .net background).

In my 'spec', i have:

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

This works fine for all my 'examples', except one where i want it to return false.

expect(File).to receive(:exist?).with("non_existent.yaml").and_return (false)

This obviously fails my test because although "non_existent.yaml" expectation was met, the "dummy.yaml" was not:

(<File (class)>).exist?("dummy.yaml")
       expected: 1 time with arguments: ("dummy.yaml")
       received: 0 times

So how can i do a 'Reset' on 'File.exist?' (a class method mock) before i setup the new expectation for it? (... "non_existent.yaml"..)

i googled and it yielded:

RSpec::Mocks.proxy_for(your_object).reset

but this gives me:

NoMethodError:
   undefined method `proxy_for' for RSpec::Mocks:Module
  • 1
    You should never use `expect to receive` in `before` block. This belong to tests. – BroiSatse Aug 14 '14 at 13:19
  • 3
    try `RSpec::Mocks.space.proxy_for(your_object).reset` – Uri Agassi Aug 14 '14 at 13:24
  • BroiSatse: i assumed 'before' is akin to '[Setup]' (nUnit: .net): setups that are common among different tests should be placed here, helps keep tests DRY and flexible to changes? Or does RSpec uses some other terminology for what i am trying to accomplish? – Bhavneet Singh Bajwa Aug 14 '14 at 17:24
  • @UriAgassi Thank You, that worked! Would You please post it as an answer so that i can accept it. Also, i wonder why such a tedious syntax for simply resetting a mock? shouldn't it be straight-forward function call? or am i trying RSpec to behave like nUnit (and other testing frameworks)? – Bhavneet Singh Bajwa Aug 14 '14 at 17:29

4 Answers4

13

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

Uri Agassi
  • 36,848
  • 14
  • 76
  • 93
3

This worked for me to unmock a specific method from a class:

mock = RSpec::Mocks.space.proxy_for(MyClass)
mock.instance_variable_get(:@method_doubles)[:my_method].reset

Note: Same logic of RSpec::Mocks.space.proxy_for(MyClass).reset which resets all methods

0

Expanding on @Uri Agassi's answer and as I answered on another similar question, I found that I could use RSpec::Mocks.space.registered? to check if a method was a mock, and RSpec::Mocks.space.proxy_for(my_mocked_var).reset to reset it's value.

Here is the example I included in my other answer:

Example: Resetting a mocked value

For example, if we wanted to reset this mock back to it's unmocked default value, we can use the RSpec::Mocks.space.proxy_for helper to find our mock, then reset it:

# when
#   Rails.configuration.action_controller.allow_forgery_protection == false
# and
#   allow(Rails.configuration.action_controller).to receive(:allow_forgery_protection).and_return(true)

RSpec::Mocks.space.registered?(Rails.configuration.action_controller)
# => true

Rails.configuration.action_controller.allow_forgery_protection
# => true

RSpec::Mocks.space.proxy_for(Rails.configuration.action_controller).reset

Rails.configuration.action_controller.allow_forgery_protection
# => false

Notice however that the even though the mock value has been reset, the mock remains registered?:

RSpec::Mocks.space.registered?(Rails.configuration.action_controller)
# => true
Glenn 'devalias' Grant
  • 1,928
  • 1
  • 21
  • 33
0

When using "expect_any_instance" I had success using the following method to change the mock (e.g. our example: Putting out a Twitter post and returning a different tweet id)

expect_any_instance_of(Twitter::REST::Client).to receive(:update).and_return(Hashie::Mash.new(id: "12"))
# post tweet

RSpec::Mocks.space.verify_all
RSpec::Mocks.space.reset_all

expect_any_instance_of(Twitter::REST::Client).to receive(:update).and_return(Hashie::Mash.new(id: "12346"))
# post another tweet
stwienert
  • 3,402
  • 24
  • 28