ruby-on-railsruby-on-rails-4rspec

How can you monkey patch a controller in rspec?


Scenario

Have a race case where concurrency can cause a duplicate key error. Take for example:

def before_create_customer_by_external_id
end

def create_customer_from_external_id(external_id = nil)
  @customer = current_account.customers.create!(external_id: external_id || @external_id)
end

def set_new_or_old_customer_by_external_id
  if @customer.blank?
    before_create_customer_by_external_id
    create_customer_from_external_id
  end
rescue ActiveRecord::RecordInvalid => e
  raise e unless Customer.external_id_exception?(e)
  @customer = current_account.customers.find_by_external_id(@external_id)
end

The Test

Now, to test the race case (based on the answer to Simulating race conditions in RSpec unit tests) we just need to monkey patch before_create_customer_by_external_id to call create_customer_from_external_id.

The Question

How can you do this without overriding the whole class and getting a "method not found" error?


Solution

  • A step on from monkey patching the class is to create an anonymous subclass:

    context "with race condition" do
       controller(ControllerToOverride) do
          def before_create_customer_by_external_id
          end
       end
    
       it "should deal with it " do
         routes.draw { # define routes here }
         ...
       end
    end
    

    This is not so very different to your solution but keeps the monkeypatch isolated to that context block.

    You may not need the custom routes block - rspec sets up some dummy routes for the rest methods (edit, show, index etc)

    If this context is inside a describe ControllerToOverride block then the argument to controller is optional, unless you have turned off config.infer_base_class_for_anonymous_controllers