Skip to content

Bug when using to_s as a subject name #2478

@corroded

Description

@corroded

What Ruby, Rails and RSpec versions are you using?

Ruby version: 2.6.6
Rails version: 6.0.3.5
RSpec version: 3.10.1

Observed behaviour

We have dependabot and the recent upgrade failed some of our specs. Particularly specs that specifically test the method to_s on an object. An example:

AR Object

class SomeActiveRecordObject < ApplicationRecord
  validates :name, presence: true
  delegate :to_s, to: :name
end

Spec

RSpec.describe SomeActiveRecordObject do
  subject(:some_active_record_object) { described_class.new }

  describe '#to_s' do
    subject(:to_s) { some_active_record_object.to_s }

    before { some_active_record_object.name = 'new name' }

    it 'delegates to name' do
      expect(some_active_record_object.to_s).to eq 'new name'
    end
  end
end

Expected behaviour

The expected behaviour is for the spec to pass seeing it's very simple. This spec fails with the new code because name is blank in the beginning and is somehow being memoised already.

Can you provide an example app?

The above already shows the example. Further more, we already have an idea on what the bug is but not sure how to fix it.

This is the specific commit that breaks it: https://github.com/rspec/rspec-rails/pull/2461/files

More specifically this change:

        def run_in_transaction?
          use_transactional_tests && !self.class.uses_transaction?(self)
        end

The issue is when the subject is named to_s (as in subject(:to_s) { something.to_s }), uses_transaction somehow uses the actual active record object - instead of the RSpec example.

This is the "offending line": https://github.com/rails/rails/blob/df41acdad93783dcac49f03036c3f34cb1c6c667/activerecord/lib/active_record/test_fixtures.rb#L96

      def uses_transaction?(method)
        @uses_transaction = [] unless defined?(@uses_transaction)
        @uses_transaction.include?(method.to_s) # for some reason "method" here becomes the AR object - so it 'memoises' the value already even before you hit the before block
      end
    end

I have added an example spec to the existing spec for name collision - I think it's related there. Here is the sample:

main...corroded:main

Final note

You don't actually need to inherit from AR for this to happen - but you do need to have included include ActiveRecord::TestFixtures since it's that method that is the problem. Not sure if it's an rspec-rails issue or a bug with active-record fixtures (or both!)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions