Search code examples
ruby-on-railsrubyrspecruby-on-rails-5

InstanceDouble(session) (anonymous)> received unexpected message :[]= with


I have a problem with rspec behavior. I try to write test for service where I use session, for read some value and overwrite this value.

For example what I want to test

class CurrentCartService
  attr_reader :user, :session

  def initialize(user, session)
    @user = user
    @session = session
  end

  def cart_id
    { id: session[:cart_id] }
  end

  def assigne_cart_to_session
    session[:cart_id] = current_cart.id
  end

spec

describe CurrentCartService do
  let(:current_user) { user }
  let(:session) { double('session') }

  let!(:cart) { create(:cart) }

  subject { described_class.new current_user, session }

  before do
    allow(session).to receive(:[]).and_return(cart.id)
  end

  describe '#call' do
    context 'when user is not signed' do
      let(:user) { nil }
      it { subject.call }
    end
  end
end

binding.pry

session[:cart_id]
=> 574
session[:cart_id] = 123
RSpec::Mocks::MockExpectationError: #<InstanceDouble(session) (anonymous)> received unexpected message :[]= with (:cart_id, 123)

How to fix this? I tried to write some expect

expect(session).to receive(:[])

But it does not work, it's still the same error


Solution

  • The message is telling you that your double received the message []= with arguments :cart_id and 123 but didn't expect it.

    Let's apply some basic logic. The problem is:

    1. receiving a message
    2. that wasn't expected

    So, there are two things you can do to get rid of the message:

    1. not send the message OR
    2. tell the double to expect the message

    The first one is easy: just remove line 14 which says session[:cart_id] = current_cart.id since that is the only place you are calling []=. However, I don't think that is what you want to do.

    Number two is also easy. There is a method called expect which allows you to tell a double to expect a certain message. So, all we need to do, is to set up an expectation.

    Good failure messages are really important in a testing framework, and thankfully, RSpec does have good failure messages. The failure message should tell you how to move forward with your tests, and the message you quoted contains all the information we need:

    • the name of the double that raised the failure (session)
    • the name of the unexpected message that caused the failure ([]=)
    • and even the arguments that were passed to the message (:cart_id and 123)

    All we have to do is, without even thinking about it, literally just copy&paste this information:

    expect(session).to receive(:[]=).with(:cart_id, 123)
    

    Or, if we want our tests to be a little less brittle and not hard-code the 123, we could also do something like this:

    expect(session).to receive(:[]=).with(:cart_id, instance_of(Integer))