Skip to content

Commit

Permalink
Fix === for multiple mocks of the same model
Browse files Browse the repository at this point in the history
When the same model is mocked more than once in the same context,
comparing with the model class using `#===` would only return true for
the first of the mocks. All following mocks would return false when
compared to the original model class using `#===`.

For `#===` to work correctly, we register each mock in a "mock_store"
on the `RSpec::ActiveModel::Mocks::Mocks` module. The store is
implemented as an RSpec stub, so it is reset after each spec example.

Fixes #60
  • Loading branch information
bquorning committed Oct 1, 2024
1 parent b6d57c8 commit fdcc72d
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
### Development
[Full Changelog](https://github.com/rspec/rspec-activemodel-mocks/compare/v1.2.0...main)

Bug fixes:

* Fix `===` to work with multiple mocks. (@bquorning, #61)

### 1.2.0 / 2023-12-10
[Full Changelog](https://github.com/rspec/rspec-activemodel-mocks/compare/v1.1.0...v1.2.0)

Expand Down
7 changes: 6 additions & 1 deletion lib/rspec/active_model/mocks/mocks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,16 @@ def self.param_delimiter; "-"; end
:blank? => false }.merge(stubs)

double("#{model_class.name}_#{stubs[:id]}", stubs).tap do |m|
mock_store_klass = RSpec::ActiveModel::Mocks::Mocks
if model_class.method(:===).owner == Module && !stubs.key?(:===)
allow(mock_store_klass).to receive(:mock_store) do
@mock_store ||= Hash.new { |h, k| h[k] = [] }
end
allow(model_class).to receive(:===).and_wrap_original do |original, other|
m === other || original.call(other)
mock_store_klass.mock_store[model_class].any? { |ms| ms === other } || original.call(other)
end
end
mock_store_klass.mock_store[model_class] << m if mock_store_klass.respond_to?(:mock_store)
msingleton = class << m; self; end
msingleton.class_eval do
include ActiveModelInstanceMethods
Expand Down
13 changes: 13 additions & 0 deletions spec/rspec/active_model/mocks/mock_model_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,19 @@ def self.===(_other)
end
# rubocop:enable Lint/LiteralAsCondition
end

it "works for multiple mocks of the same model" do
foo = mock_model(MockableModel)
bar = mock_model(MockableModel)
baz = mock_model(MockableModelNoPrimaryKey)
quz = mock_model(MockableModel)

expect(MockableModel === foo).to be(true)
expect(MockableModel === bar).to be(true)
expect(MockableModel === baz).to be(false)
expect(MockableModelNoPrimaryKey === baz).to be(true)
expect(MockableModel === quz).to be(true)
end
end

describe "#kind_of?" do
Expand Down
13 changes: 13 additions & 0 deletions spec/rspec/active_model/mocks/stub_model_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -181,5 +181,18 @@ def model_class
end
# rubocop:enable Lint/LiteralAsCondition
end

it "works for multiple mocks of the same model" do
foo = stub_model(MockableModel)
bar = stub_model(MockableModel)
baz = stub_model(MockableModelNoPrimaryKey)
qux = stub_model(MockableModel)

expect(MockableModel === foo).to be(true)
expect(MockableModel === bar).to be(true)
expect(MockableModel === baz).to be(false)
expect(MockableModelNoPrimaryKey === baz).to be(true)
expect(MockableModel === qux).to be(true)
end
end
end

0 comments on commit fdcc72d

Please sign in to comment.