rspec-rails: `let(:name)` will conflict with Rails internals for fixtures

What Ruby, Rails and RSpec versions are you using?

Ruby version: 2.7.1 Rails version: 6.1.1 RSpec version: 4.0.2

Observed behaviour

One of my specs started to fail while upgrading rails from 6.0 to 6.1.

I noticed that a let(:name)-declaration in my spec was being called from ActiveRecord::TestFixtures#run_in_transaction? which is AFAIU supposed to call RSpec::Rails::FixtureSupport::Fixtures#name instead.

Expected behaviour

I’d want to be warned if I’m redefining a reserved method via RSpec::Core::MemoizedHelpers#let similar to how initialize is a reserved keyword.

Can you provide an example app?

Start with a blank rails app (Version => 6.1):

gem install rails -v '6.1.1'
rails new rspec-rails-issue

Install rspec-rails (Version >= 4.0.2):

group :development, :test do
  gem 'rspec-rails', '~> 4.0.2'
end

Install RSpec:

rails generate rspec:install

Create spec/some_spec.rb:

require 'rails_helper'

RSpec.describe "observes a call to :name" do
  subject { true }

  let(:name) { puts caller; puts "I should never have been called" }

  it { is_expected.to be_truthy }
end

Run the spec:

rspec spec/some_spec.rb
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/memoized_helpers.rb:317:in `block (2 levels) in let'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/memoized_helpers.rb:157:in `block (3 levels) in fetch_or_store'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/memoized_helpers.rb:157:in `fetch'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/memoized_helpers.rb:157:in `block (2 levels) in fetch_or_store'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-support-3.10.2/lib/rspec/support/reentrant_mutex.rb:23:in `synchronize'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/memoized_helpers.rb:156:in `block in fetch_or_store'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/memoized_helpers.rb:155:in `fetch'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/memoized_helpers.rb:155:in `fetch_or_store'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/memoized_helpers.rb:317:in `block in let'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.1/lib/active_record/test_fixtures.rb:102:in `run_in_transaction?'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.1/lib/active_record/test_fixtures.rb:116:in `setup_fixtures'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.1/lib/active_record/test_fixtures.rb:10:in `before_setup'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-rails-4.0.2/lib/rspec/rails/adapters.rb:74:in `block (2 levels) in <module:MinitestLifecycleAdapter>'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example.rb:455:in `instance_exec'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example.rb:455:in `instance_exec'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/hooks.rb:390:in `execute_with'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example.rb:350:in `call'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/hooks.rb:486:in `run'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example.rb:465:in `with_around_example_hooks'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example.rb:508:in `with_around_and_singleton_context_hooks'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example.rb:259:in `run'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example_group.rb:644:in `block in run_examples'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example_group.rb:640:in `map'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example_group.rb:640:in `run_examples'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example_group.rb:606:in `run'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/runner.rb:121:in `block (3 levels) in run_specs'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/runner.rb:121:in `map'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/runner.rb:121:in `block (2 levels) in run_specs'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/configuration.rb:2067:in `with_suite_hooks'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/runner.rb:116:in `block in run_specs'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/reporter.rb:74:in `report'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/runner.rb:115:in `run_specs'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/runner.rb:89:in `run'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/runner.rb:71:in `run'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/runner.rb:45:in `invoke'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/exe/rspec:4:in `<top (required)>'
[…path omitted]/.asdf/installs/ruby/2.7.1/bin/rspec:23:in `load'
[…path omitted]/.asdf/installs/ruby/2.7.1/bin/rspec:23:in `<main>'
I should never have been called
.

Finished in 0.02096 seconds (files took 1.89 seconds to load)
1 example, 0 failures

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 15 (8 by maintainers)

Commits related to this issue

Most upvoted comments

My theory is it’s a very, very old relic - entered with f3de931145ea7143aef2e109eeba7e9d1ec79f4e, but irrelevant in rspec-core since d941ba7. I haven’t seen what happens to tests if it gets removed/changed, though.

@pirj I do use the branch now. the patch I mentioned needs it to work 🙂 but just using the branch doesn’t solve the issue, unfortunately.

This is an excellent representation of what I was seeing in #2417 (comment here). Like @grekko said above, it seems like the fundamental issue is the ActiveSupport method run_in_transaction? (changed between Rails 6.0 and 6.1), which is run in the example namespace, calling name; any let(:name) will override the method in RSpec::Rails::FixtureSupport:

# spec/some_spec.rb
require 'rails_helper'

RSpec.describe "observes a call to :name" do
  subject { true }

  let(:name) do
    method(__method__).owner # => RSpec::ExampleGroups::ObservesACallToName < RSpec::Core::ExampleGroup
    method(__method__).super_method.owner # => RSpec::ExampleGroups::ObservesACallToName::LetDefinitions
    method(__method__).super_method.super_method.owner # => RSpec::Rails::FixtureSupport
    correct_method = method(__method__).super_method.super_method
    puts "calling to #{correct_method.source_location.join(?:)}"
    # => calling to /yadda/yadda/gems/rspec-rails-3ddfb3abe2b0/lib/rspec/rails/fixture_support.rb:67
    correct_method.call
  end

  it do
    is_expected.to be_truthy
  end
end

I wanted to verify that https://github.com/rspec/rspec-rails/pull/2423 would fix the issue I described, so I adjusted the setup from the PR description:

What I changed

I replaced the reference to rspec 4.0.2 with rails-6-1-dev:

diff --git a/Gemfile b/Gemfile
index 082f631..8b9771b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -55,6 +55,9 @@ end
 # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
 gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
 
-group :development, :test do
-  gem 'rspec-rails', '~> 4.0.2'
+%w[rspec rspec-core rspec-expectations rspec-mocks rspec-support].each do |lib|
+  gem lib, git: "https://github.com/rspec/#{lib}.git", branch: "main"
+end
+git "https://github.com/rspec/rspec-rails", branch: "rails-6-1-dev" do
+  gem "rspec-rails"
 end

… and rerunning the spec above displays the same behaviour:

bundle exec rspec spec/some_spec.rb
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/memoized_helpers.rb:339:in `block (2 levels) in let'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/memoized_helpers.rb:179:in `block (3 levels) in fetch_or_store'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/memoized_helpers.rb:179:in `fetch'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/memoized_helpers.rb:179:in `block (2 levels) in fetch_or_store'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-support-d04c8aef72d0/lib/rspec/support/reentrant_mutex.rb:23:in `synchronize'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/memoized_helpers.rb:178:in `block in fetch_or_store'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/memoized_helpers.rb:177:in `fetch'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/memoized_helpers.rb:177:in `fetch_or_store'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/memoized_helpers.rb:339:in `block in let'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.1/lib/active_record/test_fixtures.rb:102:in `run_in_transaction?'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.1/lib/active_record/test_fixtures.rb:116:in `setup_fixtures'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.1/lib/active_record/test_fixtures.rb:10:in `before_setup'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-rails-3ddfb3abe2b0/lib/rspec/rails/adapters.rb:74:in `block (2 levels) in <module:MinitestLifecycleAdapter>'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/example.rb:455:in `instance_exec'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/example.rb:455:in `instance_exec'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/hooks.rb:390:in `execute_with'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/example.rb:350:in `call'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/hooks.rb:486:in `run'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/example.rb:465:in `with_around_example_hooks'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/example.rb:508:in `with_around_and_singleton_context_hooks'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/example.rb:259:in `run'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/example_group.rb:644:in `block in run_examples'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/example_group.rb:640:in `map'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/example_group.rb:640:in `run_examples'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/example_group.rb:606:in `run'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/runner.rb:121:in `block (3 levels) in run_specs'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/runner.rb:121:in `map'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/runner.rb:121:in `block (2 levels) in run_specs'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/configuration.rb:2067:in `with_suite_hooks'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/runner.rb:116:in `block in run_specs'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/reporter.rb:74:in `report'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/runner.rb:115:in `run_specs'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/runner.rb:89:in `run'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/runner.rb:71:in `run'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/lib/rspec/core/runner.rb:45:in `invoke'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bundler/gems/rspec-core-ecb65d2d4ea7/exe/rspec:4:in `<top (required)>'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bin/rspec:23:in `load'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/bin/rspec:23:in `<top (required)>'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/bundler-2.1.4/lib/bundler/cli/exec.rb:63:in `load'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/bundler-2.1.4/lib/bundler/cli/exec.rb:63:in `kernel_load'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/bundler-2.1.4/lib/bundler/cli/exec.rb:28:in `run'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/bundler-2.1.4/lib/bundler/cli.rb:476:in `exec'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/bundler-2.1.4/lib/bundler/vendor/thor/lib/thor/command.rb:27:in `run'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/bundler-2.1.4/lib/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/bundler-2.1.4/lib/bundler/vendor/thor/lib/thor.rb:399:in `dispatch'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/bundler-2.1.4/lib/bundler/cli.rb:30:in `dispatch'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/bundler-2.1.4/lib/bundler/vendor/thor/lib/thor/base.rb:476:in `start'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/bundler-2.1.4/lib/bundler/cli.rb:24:in `start'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/bundler-2.1.4/exe/bundle:46:in `block in <top (required)>'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/bundler-2.1.4/lib/bundler/friendly_errors.rb:123:in `with_friendly_errors'
[…path omitted]/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/bundler-2.1.4/exe/bundle:34:in `<top (required)>'
[…path omitted]/.asdf/installs/ruby/2.7.1/bin/bundle:23:in `load'
[…path omitted]/.asdf/installs/ruby/2.7.1/bin/bundle:23:in `<main>'
I should never have been called
.

Finished in 0.03632 seconds (files took 1.38 seconds to load)
1 example, 0 failures

Please use the unreleased version in rails-6-1-dev there is a fix for this. (Closed as duplicate of #2417)