Custom RSpec Matchers for arrays

More than once, I’ve had to check an array for it’s order in RSpec. Usually, I just sort the array, and compare it to itself. The spec might look something like

describe MyObject
  describe "scope #ordered" do
    it "returns results in most recent order"
      expect(MyObject.ordered.all.sort(&:attr_to_order_by)).to eql my_objects
    end
  end
end

This may seem a bit opaque (as a reader) – how does the sorting relate to the test? Also, if you looking at timestamps (like sort by created_at), and you’re using fixtures or FactoryGirl.create, you may end up with duplicate timestamps. Suddenly your sort will be ill defined because the secondary sort key is undefined. A better method would be to check that the :created_at time is monotonically decreasing.

An array x is monotonically decreasing if and only if:
x_i \geq x_j \forall i < j

For these kinds of cases, I decided to write a some RSpec matchers. Following the RSpec Wiki docs here, it was a snap. I wrote matchers for monotonically increasing/decreasing and strictly increasing/decreasing tests.

The following four matchers (available from this gist (included below)) give you

   be_monotonically_increasing
   be_strictly_increasing
   be_monotonically_decreasing
   be_strictly_decreasing

So now I can rewrite my spec

describe MyObject
  describe "scope #ordered" do
    it "returns results in most recent order"
      # the most recent object should have the largest created_by timestamp
      expect(MyObject.ordered.map(&:created_by)).to be_monotonically_decreasing
    end
  end
end

These matchers follow the definitions from NIST for monotonically/strictly increasing/decreasing (see links below). The short story is:

Monotonically Increasing means:
x_i \leq x_j \forall < j

Strictly Increasing means:
x_i < x_j \forall i < j

Monotonically Decreasing means:
x_i \geq x_j \forall i < j

Strictly Decreasing means:
x_i > x_j \forall i < j

To use these, put the gist (below) custom_array_matchers.rb file in your spec/support directory and you’re off to the races. You can start using those matchers in your specs. The gist also includes specs that test the matchers. If the matcher names aren’t clear, check out the specs and hopefully that’ll reveal what they do. The algorithm itself is pretty simple – take a derivative of the array values and make sure that it’s always positive (or negative) in slope for increasing (or decreasing) checks.

The code is built for RSpec 2.something (at a minimum). They also assume your sort attribute is an number. I haven’t tried it with strings, but I think it wouldn’t take much to modify things to handle more general cases. Perhaps I’ll run into that problem and have an update. Until then, they are what they are.

References

Definitions
Gist

Advertisements

5 thoughts on “Custom RSpec Matchers for arrays

  1. You can use RSpec’s equality matcher when order in arrays matter.

    “`
    Failure/Error: expect([1, 2, 3]).to eq([1, 3, 2])

    expected: [1, 3, 2]
    got: [1, 2, 3]

    (compared using ==)
    “`

    The `eql` matcher also works.

  2. That’s true. If the array is big, it can be tedious to write out all the expected values, so if I expect the order to be descending or ascending (like things are ordered by timestamps or something), these helpers can be handy.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s