Rspec - Check if an array have same elements than other, regardless of the order

10,888

Solution 1

Here was my wrong matcher (thanks @steenslag):

RSpec::Matchers.define :be_same_array_as do |expected_array|
  match do |actual_array|
    (actual_array | expected_array) - (actual_array & expected_array) == []
  end
end

Other solutions:

  • use the builtin matcher, best solution

  • use Set:

Something like:

require 'set'
RSpec::Matchers.define :be_same_array_as do |expected_array|
  match do |actual_array|
    Set.new(actual_array) == Set.new(expected_array)
  end
end

Solution 2

There is a match_array matcher in RSpec which does this:

http://rubydoc.info/github/rspec/rspec-expectations/RSpec/Matchers:match_array

Solution 3

You can use the =~ operator:

[:b, :a, :c].should =~ [:a, :b, :c] # pass

From the docs:

Passes if actual contains all of the expected regardless of order. This works for collections. Pass in multiple args and it will only pass if all args are found in collection.

For RSpec's expect syntax there's match_array:

expect([:b, :a, :c]).to match_array([:a, :b, :c]) # pass

or contain_exactly if you're passing single elements:

expect([:b, :a, :c]).to contain_exactly(:a, :b, :c) # pass

Solution 4

I think all answers seems to be pretty old. Latest matcher is contain_exactly.

You can simply do -

expect([:b, :a, :c]).to contain_exactly(:a, :b, :c)

Please not that in contain_exactly we don't pass a whole array, instead pass separate elements.

Ref - Rspec Guide

Share:
10,888
pierallard
Author by

pierallard

Updated on July 11, 2022

Comments

  • pierallard
    pierallard almost 2 years

    I'm not sure if its a Rspec question, but I only encountred this problem on Rspec tests.

    I want to check if an array is equal to another array, regardless of the elements order :

    [:b, :a, :c] =?= [:a, :b, :c]
    

    My current version :

    my_array.length.should == 3
    my_array.should include(:a)
    my_array.should include(:b)
    my_array.should include(:c)
    

    Is there any method on Rspec, ruby or Rails for do something like this :

    my_array.should have_same_elements_than([:a, :b, :c])
    

    Regards

  • pierallard
    pierallard about 11 years
    What an elegant solution ! Thanks ! I knew &, but you learned me the operator |.
  • apneadiving
    apneadiving about 11 years
    damn, didn't know that, +1
  • steenslag
    steenslag about 11 years
    I didn't downvote, but this fails when there are duplicates: [3,2,1,1] and [1,2,3] returns true.
  • apneadiving
    apneadiving about 11 years
    @steenslag: ([3,2,1,1] | [1,2,3] - [3,2,1,1] & [1,2,3]) => [3, 2, 1] and ([1,2 ,3] | [3, 2, 1, 1] - [1, 2, 3] & [3, 2, 1, 1])=> [1, 2, 3] this doesn't return true since it's different from []
  • pierallard
    pierallard about 11 years
    Best solution for many people, but I can't install RSpec > 2.11.
  • apneadiving
    apneadiving about 11 years
    spec/support/custom_matchers.rb is where I store this
  • steenslag
    steenslag about 11 years
    @apneadiving In your post you are doing ([3,2,1,1] | [1,2,3]) - ([3,2,1,1] & [1,2,3]) which is different from your comment (result: [])
  • steenslag
    steenslag about 11 years
    @apneadiving [3,2,1,1] and [1,2,3,2] still failing. I guess this is why Array does not have a ^ method like Set .
  • apneadiving
    apneadiving about 11 years
    @steenslag: you've made my day :)
  • Nick Messick
    Nick Messick about 11 years
    There is no need to go through all this. Just use the builtin operator =~ like in Stefan's answer.
  • Nick Messick
    Nick Messick about 11 years
    The answer includes two custom matchers, which is extremely bad practice when a builtin operator is available.
  • apneadiving
    apneadiving about 11 years
    @messick: yes I didn't delete my first anser ubt I wrote use the builtin matcher, best solution what else do you need?
  • Ulysse BN
    Ulysse BN about 6 years
    contain_exactly does the trick as well, for a splatted array (see stackoverflow.com/a/48206163/6320039)
  • Ulysse BN
    Ulysse BN about 6 years
    Downvote because: The answer is very NOT straightforward. Users tend to read quickly, and won't see your best solution part. Plus It is unclear.