Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Investigate rspec-mocks incompatibility with add_method_tracer #2695

Closed
kaylareopelle opened this issue Jun 5, 2024 · 3 comments
Closed
Assignees
Labels
3 Story Point Estimate

Comments

@kaylareopelle
Copy link
Contributor

kaylareopelle commented Jun 5, 2024

When add_method_tracer is used on a method that is overridden in an RSpec test using allow_any_instance_of, the following error is raised:

Failures:

  1) Bowling#score with no strikes or spares does not like to be prepended
     Failure/Error:
       allow_any_instance_of(Bowling)
         .to receive(:hit)
         .and_return(100)

       Using `any_instance` to stub a method (hit) that has been defined on a prepended module (#<Module:0x0000000127e41240>) is not supported.
     # ./spec/bowling_spec.rb:12:in `block (3 levels) in <top (required)>'

add_method_tracer prepends onto the methods it traces to create segments and metrics.

This error occurs even when the :agent_enabled configuration is set to false.

A customer reported they would like to use add_method_tracer and allow_any_instance_of on the same method.

@kaylareopelle kaylareopelle self-assigned this Jun 5, 2024
@workato-integration
Copy link

@kaylareopelle kaylareopelle added the 3 Story Point Estimate label Jun 5, 2024
@kaylareopelle
Copy link
Contributor Author

kaylareopelle commented Jun 5, 2024

The example above was created with this class:

# lib/bowling.rb

require 'newrelic_rpm'

class Bowling
  attr_reader :score

  def initialize
    @score = 0
  end

  def hit(pin_count)
    @score += pin_count
  end

  add_method_tracer :hit
end

And this spec:

# spec/bowling_spec.rb

require 'bowling'

RSpec.describe Bowling, "#score" do
  context "with no strikes or spares" do
   # passes 
    it "sums the pin count for each roll" do
      bowling = Bowling.new
      20.times { bowling.hit(4) }
      expect(bowling.score).to eq 80
    end
   
    # fails with error
    it "does not like to be prepended" do
      allow_any_instance_of(Bowling)
        .to receive(:hit)
        .and_return(300)
    end
  end
end

With this Gemfile:

source 'https://rubygems.org'

gem "newrelic_rpm", "~> 9.10"
gem "rspec", "~> 3.13"

The example was drawn from: https://rspec.info/
It was executed using: $ NEW_RELIC_AGENT_ENABLED=false bin/rspec

@kaylareopelle kaylareopelle changed the title Investigate rspec-mocks incompatibility with add_method_tracer Investigate allow_any_instance_of incompatibility with add_method_tracer Jun 5, 2024
@kaylareopelle kaylareopelle changed the title Investigate allow_any_instance_of incompatibility with add_method_tracer Investigate rspec-mocks incompatibility with add_method_tracer Jun 5, 2024
@kaylareopelle
Copy link
Contributor Author

The agent needs to be able to add method tracers in cases where the API is invoked before the agent is loaded. This means the behavior of add_method_tracer cannot be contingent upon whether the agent is enabled.

We have a feature request open that may get around this. #2701 would allow method tracers to be added by configuration rather than declaring them directly in your code.

For anyone who encounters this problem, we have a few workarounds to recommend:

  1. You could refactor your tests to stub a single instance of a class rather than any instance of a class. The following code didn't raise the RSpec prepend error:

Option A:

    let(:bowling) do
      b = bowling.new

      allow(b).to receive(:hit).and_return(100)   
      
      b
    end

Option B:

      let(:bowling) { Bowling.new }

      before do
        allow(bowling)
          .to receive(:hit)
          .and_return(100)
      end
  1. If you're using factory_bot, you could update the factory definition for the class to stub the return value.

  2. Inside the spec/spec_helper.rb file, you could overwrite the add_method_tracer method to remove its logic before RSpec is configured. This could be done using a one-liner, like:

  class Module; def add_method_tracer(*_args); end; end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3 Story Point Estimate
Projects
Archived in project
Development

No branches or pull requests

1 participant