rspec-railsrspec-mocks

Mocking object that are generated dynamically + rspec-mock


I am using jruby(9.1.12.0) with rspec-rails(3.7.0).

I have the following method

class AddressCreator
  include Singleton
  attr_reader :client

  def initialize
    @client = SomeJavaClass.new(some keys go here)       
  end
end

# SomeJavaClass has an create_address instance method.

def create_address(user_id, address)
  req = Java::RandomRequest.new(user_id, address)
  resp = Java::RandomResponse.new(default_response_params)
  AddressCreator.instance.client.create_address(some_key, req, resp)
end

And have added the tests for the same as

describe '#create_address' do
  let(:some_key) { SecureRandom.uuid }
  let(:user_id) { 1 }
  let(:default_response_params) { false }
  let(:address) { 'some address goes here' }
  let(:request) { Java::RandomRequest.new(address)}
  let(:response) { Java::RandomResponse.new(default_response_params)}

  before {allow(AddressCreator.instance.client).to receive(:create_address).with(some_key, request, response).and_return(response)}

  it 'should create the address' do
    result = AddressCreator.instance.client.create_address some_key
    expect(AddressCreator.singleton).to have_received(:create_address).with(some_key, request, response) do |request|
      expect(request.is_success).to eq(response.is_success)
    end
    expect(result).to eq(response)
  end
end

My specs fail stating

expected  <Java::RandomRequest:0x1d09fb8e>
got       <Java::RandomRequest:0x38482732>

How do we mock so that we received the same instance?


Solution

  • Instances of the Java::RandomRequest and Java::RandomResponse that you create in spec do not match those that you create in create_address. Try stubbing the classes to return the values you need like:

    let(:request) { instance_double(Java::RandomRequest) }
    let(:response) { instance_double(Java::RandomResponse) }
    before do
      allow(Java::RandomRequest).to receive(:new).and_return(request)
      allow(Java::RandomResponse).to receive(:new).and_return(response)
    end
    

    Also, you can collapse allow in before with expect... have_received after create_address call to a spy called before the create_address action, e.g.:

    # no before block
    it 'should create the address' do
    expect(AddressCreator.singleton)
      .to receive(:create_address)
      .with(some_key, request, response) do |request|
        expect(request.is_success).to eq(response.is_success)
      end
      result = AddressCreator.instance.client.create_address some_key
      expect(result).to eq(response)
    end
    

    Another option would be to use kind_of/instance_of/duck_type argument matchers instead of stubs if you don't intend to check how exactly request and response are initialized, e.g. something like:

    # no before
    it 'should create the address' do
      expect(AddressCreator.singleton).to receive(:create_address)
      .with(kind_of(String), instance_of(Java::RandomRequest), duck_type(:is_success)) do |request|
        expect(request.is_success).to be_true
      end
      AddressCreator.instance.client.create_address some_key
    end
    

    Lastly, you don't have to initialize some_key with real secure key, double would be enough if you don't process and just pass to some stubbed class in the end: let(:some_key) { double }