Read more

RSpec: Expecting non-primitive objects as method invocation arguments

Henning Koch
May 13, 2016Software engineer at makandra GmbH

Expecting a primitive value as an argument to a method invocation is easy:

expect(object).to receive(:foo).with('arg1', 'arg2')
Illustration online protection

Rails Long Term Support

Rails LTS provides security patches for old versions of Ruby on Rails (2.3, 3.2, 4.2 and 5.2)

  • Prevents you from data breaches and liability risks
  • Upgrade at your own pace
  • Works with modern Rubies
Read more Show archive.org snapshot

This expectation would be met with this call:

object.foo('arg1', 'arg2')

But what if the argument is expected to be an object with a given set of methods? E.g. this class with #first_name and #last_name methods:

class Person

  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
  end
  
  attr_reader :first_name, :last_name
  
end

To just test if an argument is a Person, you have several options:

Test that the argument is an instance of a certain class

expect(object).to receive(:foo).with(instance_of(Person))

Test that the argument responds to certain methods

expect(object).to receive(:foo).with(duck_type(:first_name, :last_name))

To also test that the methods return a particular value, use have_attributes:

expect(object).to receive(:foo).with(have_attributes(first_name: 'Hans', last_name: 'Dampf'))

Test that the argument is a certain class and also returns certain values

Use the attached object_having matcher to test both class and method return values:

expect(object).to receive(:foo).with(object_having(Person, first_name: 'Hans', last_name: 'Dampf'))

Test that the argument is equal to another object

Since with tests == equality by default, you might want to define Person#== so you can compare it to other persons:

class Person

  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
  end
  
  def ==(other)
    first_name == other.first_name && last_name == other.last_name
  end
  
end

Then, in your spec:

expect(object).to receive(:foo).with(Person.new(first_name: 'Hans', last_name: 'Dampf'))

Test anything you want with a block

expect(object).to receive(:foo).with do |received_object|
  expect(received_object.first_name).to eq('Hans')
  ...
end

See Rspec: Complex argument expectations for should_receive for more details.

Henning Koch
May 13, 2016Software engineer at makandra GmbH
Posted by Henning Koch to makandra dev (2016-05-13 08:51)