RSpec: Ensuring a method is called on an object that will be created in the future

Updated . Posted . Visible to the public. Repeats.

rspec >= 3.1 brings a method and_wrap_original. It seems a bit complicated at first, but there are use cases where it helps to write precise tests. For example it allows to add expectations on objects that will only be created when your code is called.

If you have older rspec, you could use expect_any_instance_of, but with the drawback, that you can't be sure if it really was the correct instance which got the message.

Example

The example model uses different validators based on a flag:

class MyModel < ApplicationRecord

  # ...

  def enhanced_valid?(skip_expensive_checks: false)
    enhanced_errors(skip_expensive_checks: skip_expensive_checks).none?
  end

  def enhanced_errors(skip_expensive_checks: false)
    validator = if skip_expensive_checks
      ActiveType.cast(self, SuperValidator)
    else
      ActiveType.cast(self, SuperDuperValidator)
    end
    validator.valid?
    validator.errors.full_messages
  end

end

The spec has to ensure that the correct Validator is used:

context 'when skipping expensive checks' do
  let(:record) { MyModel.new }

  it 'uses the SuperValidator' do
    expect(ActiveType).to receive(:cast)           # <- the method (`cast`) is mocked (replaced)
      .with(record, SuperValidator)                # <- ensure the expected arguments are given 
      .and_wrap_original do |method, *arguments|   # <- here is `and_wrap_original`
        validator = method.call(*arguments)        # <- the original method (`cast`) is explicitly called
        expect(validator).to receive(:valid?)      # <- a expectation on the then newly created object is added
        validator                                  # <- the new object is returned
      end

    record.enhanced_valid?(skip_expensive_checks: true)
  end
end

Also see here for further examples.

Please note: Usually it is preferrable to test behaviour, not method calls. Since the behaviour of both validators is thoroughly tested in their own specs, mocking in this case avoids unnecessary duplication.

Judith Roth
Last edit
Jonas Schiele
Keywords
intercept
License
Source code in this card is licensed under the MIT License.
Posted by Judith Roth to makandra dev (2021-05-05 15:28)