Reputation: 321
I have a test suite with too many assertions like this
expect(array_1).to match_array array_2
expect(array_3).to match_array array_4
expect(array_5).to match_array array_5
and so forth.
I'd like to wrap these checks in a custom matcher but within that custom matcher would like to use the match_array
matcher as I really like the error message it returns listing missing and extra elements in case of a mismatch.
Something like this:
RSpec::Matchers.define :have_data do |data|
match do |actual|
actual.eql? data # I don't want to do this.
actual.match_array? data # <<- I'd like do do something like this to retain the matcher behaviour
end
end
Any way I can do this? match_array?
doesn't exist, of course.
Upvotes: 1
Views: 900
Reputation: 1201
I wanted to use the contain_exactly functionality with a remapping of the actual array to ids for the array items. This is similar to what you are trying to do, so perhaps it will help. I put this in /support/matchers/match_array_ids.rb
.
module MyMatchers
class MatchArrayIds < RSpec::Matchers::BuiltIn::ContainExactly
def match_when_sorted?
values_match?(safe_sort(expected), safe_sort(actual.map(&:id)))
end
end
def match_array_ids(items)
MatchArrayIds.new(items)
end
end
I also created a test to test the matcher was functioning as expected. I put this in spec/matcher_tests/match_array_ids_spec.rb
.
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe MyMatchers::MatchArrayIds do
before do
class ObjectWithId
attr_reader :id
def initialize(id)
@id = id
end
end
end
context 'when array of objects with ids and array of ids match' do
let(:objects_with_ids) { [ObjectWithId.new('id1'), ObjectWithId.new('id2')] }
let(:ids) { ['id1', 'id2'] }
it 'returns true' do
expect(objects_with_ids).to match_array_ids(ids)
end
end
context 'when array of objects with ids exist and array of ids is empty' do
let(:objects_with_ids) { [ObjectWithId.new('id1'), ObjectWithId.new('id2')] }
let(:ids) { [] }
it 'returns false' do
expect(objects_with_ids).not_to match_array_ids(ids)
end
end
context 'when array of objects with ids is empty and array of ids exist' do
let(:objects_with_ids) { [] }
let(:ids) { ['id1', 'id2'] }
it 'returns false' do
expect(objects_with_ids).not_to match_array_ids(ids)
end
end
context 'when array of objects with ids and array of ids DO NOT match' do
let(:objects_with_ids) { [ObjectWithId.new('id1'), ObjectWithId.new('id2')] }
let(:ids) { ['id1', 'id3'] }
it 'returns false' do
expect(objects_with_ids).not_to match_array_ids(ids)
end
end
end
You can find the examples for making a custom from scratch matcher that I based this on at... https://github.com/rspec/rspec-expectations/blob/45e5070c797fb4cb6166c8daca2ea68e31aeca40/lib/rspec/matchers.rb#L145-L196
Upvotes: 1
Reputation: 1290
Looking at the implementation of the match_array
matcher:
# An alternate form of `contain_exactly` that accepts
# the expected contents as a single array arg rather
# that splatted out as individual items.
#
# @example
# expect(results).to contain_exactly(1, 2)
# # is identical to:
# expect(results).to match_array([1, 2])
#
# @see #contain_exactly
def match_array(items)
contain_exactly(*items)
end
And the contain_exactly
method makes a call to the Rspec::BuiltIn::ContainExactly
module:
def contain_exactly(*items)
BuiltIn::ContainExactly.new(items)
end
You could iterate over the data you need to match using calls to this module, and still use the error messages from the match_array
method.
Alternatively, you could implement your own module based on the BuiltIn::ContainExactly
module.
Upvotes: 1