Rspec’s be_an_instance_of is pretty useful

Today I was testing a small helper method I had written that would convert a String to a date or if it was already a DateTime object it would just return it. A pretty simple function but one that needs testing all the same.

Initially my test looked something like this;


require 'spec_helper'
require 'date'
require 'time'

describe MyHelper do
  describe 'convert_to_datetime functionality' do

    let(:input) { '2012-03-04' }
    let(:date_version) { DateTime.parse(input) }

    it 'should successfully convert a well formatted date string' do
      output = convert_to_datetime input
      output.should == date_version
    end

    it 'should return the original date if a DateTime object was passed in' do
      output = convert_to_datetime date_version
      output.should == date_version
    end
  end
end

On the face of it this would seem to work, and indeed all the tests pass, but what if I changed output.should == date_version to output.should == input? Instant fail? Nope!

The reason why this also passes is because I am including ActiveSupport earlier on in the code and with that included so long as the String when converted to a DateTime is the same DateTime then .should == will pass.

This was unexpected, but fortunately I very quickly found be_an_instance_of, what this allows us to do is check that a variable is an instance of a particular class, in our example a DateTime class.

Now my code reads;


require 'spec_helper'
require 'date'
require 'time'

describe MyHelper do
  describe 'convert_to_datetime functionality' do

    let(:input) { '2012-03-04' }
    let(:date_version) { DateTime.parse(input) }

    it 'should successfully convert a well formatted date string' do
      output = convert_to_datetime input
      output.should be_an_instance_of(DateTime)
      output.should == date_version
    end

    it 'should return the original date if a DateTime object was passed in' do
      output = convert_to_datetime date_version
      output.should be_an_instance_of(DateTime)
      output.should == date_version
    end
  end
end

Which is a more solid check.

*Update*

Some nice folk on Reddit pointed out that I could have done one of the following;

  1. Swapped output.should for date_version.should which would force rspec not to change my DateTime into a String.
  2. Used eql instead of == which does not do type conversion.
  3. Using be_instance_of can make your code a little harder to read and might end up coupling your test to a particular data type and if that changes you could end up with stuff to fix.
Share this on
  • http://blog.davidchelimsky.net David Chelimsky

    Hi Toby – I commented on this on [Reddit](http://www.reddit.com/r/ruby/comments/1ierwn/rspecs_be_an_instance_of_is_pretty_useful/), but in case you didn’t see it or for anybody else who sees this post: RSpec does no such type conversion. If this is happening to you in a project that uses ActiveSupport, that is likely the source of the type conversion (see https://gist.github.com/dchelimsky/6023806).

    re: `be_an_instance_of“, I agree that it is useful as an additional sanity check in this case because it solves a very specific problem you ran into, but I wouldn’t recommend it generally because it creates additional noise and makes the code harder to change (consider that a decision to use Date instead of DateTime would require these examples changing).

  • http://www.tosbourn.com/ Toby Osbourn

    Hi David,

    Thanks so much for commenting, I haven’t looked at Reddit yet.

    That is very interesting that ActiveSupport is behind this, I (wrongly) assumed that anything after `.should` was in the Rspec realm, I will update the blog post accordingly.

    I totally agree that using `be_an_instance_of` couples your code a little too tightly, but then for most data types I would have thought that changing the type is going to cause some headaches in your test suite anyway.

    Thanks again, you have taught me a lot.

  • Marcin Grzywaczewski

    Hello.

    I think that using be_an_instance_of can be considered as smell.

    Ask yourself: “Why I need to have this DateTime object?”.

    You want DateTime, because you want the public interface of DateTime. In such case, I would rather assert existence of methods (and maybe – output of these methods), not the particular class name. We code in a duck typed language, after all.