Search code examples
rubyrspecmonkeypatching

Mocking a patch module


I've got a codebase which is tested in two scenarios: run via entry point A, and B. When it's run via A, the db connection is used as is. When it's run via B, ActiveRecord::Base.connection is monkey patched.

Since B is just a helper script, it's currently tested in rspec by running it as an external command and checking the output. I'd like to bring some sanity back and test the behaviour without spawning new processes though.

Is there a way in rspec mocks to "temporarily extend" a class? I'd like to get the behaviour of doing:

before do
  ActiveRecord::Base.connection.extend(App::SomePatch)
end
after do
  ActiveRecord::Base.connection.unextend(App::SomePatch)
end

Of course unextend doesn't exist. I have only 3 methods to patch, so I could potentially use the mocks for each method instead, but a method alias makes this complicated.

The patch module looks like this:

module SomePatch
  def SomePatch.included(mod)
    alias :old_execute :execute
  end

  def execute(*args) ... end

  def some_storage
    @some_storage ||= []
  end
end

Solution

  • I would go with cloning, something along this lines:

    before do
      @original_connection = ActiveRecord::Base.connection
      ActiveRecord::Base.connection = @original_commention.dup
      ActiveRecord::Base.connection.extend(App::SomePatch)
    end
    
    after do
      ActiveRecord::Base.connection = @original_connection
    end
    

    I did not test that, but as long there are not "quirks" with cloning the object, this should be fine.

    Edit: Ok, this does not work, because there's no connection= method, so you can probably try with mocking:

    before do
      @original_connection = ActiveRecord::Base.connection
      new_connection = @original_connection.dup
      new_connection.extend(App::SomePatch)
      allow(ActiveRecord::Base).to receive(:connection).and_return(new_connection)
    end
    

    And you probably don't need after because the mock will be "undone"