278

I often want to compare arrays and make sure that they contain the same elements, in any order. Is there a concise way to do this in RSpec?

Here are methods that aren't acceptable:

#to_set

For example:

expect(array.to_set).to eq another_array.to_set

or

array.to_set.should == another_array.to_set

This fails when the arrays contain duplicate items.

#sort

For example:

expect(array.sort).to eq another_array.sort

or

array.sort.should == another_array.sort

This fails when the arrays elements don't implement #<=>

nicholaides
  • 19,211
  • 12
  • 66
  • 82
  • 7
    Not to smartass, but comparing `to_set` and `size` actually doesn't do what you want. E.g. [a, b, b] would match [a, a, b]. Cheers! – Jo Liss Jan 27 '11 at 22:50
  • 4
    For those who stumbled here wondering the opposite: **order should be the same**. Use the `eq` matcher, e.g. `expect([1, 2]).to_not eq([2, 1])` – Dennis Jan 21 '16 at 23:26

6 Answers6

274

Try array.should =~ another_array

The best documentation on this I can find is the code itself, which is here.

x1a4
  • 19,417
  • 5
  • 40
  • 40
  • This doesn't take the order in account, so this is not an acceptable answer, is it? Quote from the [docs](https://github.com/dchelimsky/rspec/blob/master/lib/spec/matchers/match_array.rb#L56): `Passes if actual contains all of the expected regardless of order.`. – Joshua Muheim Jan 22 '13 at 15:44
  • 23
    Title of this post: "Rspec: “array.should == another_array” but without concern for order" – x1a4 Jan 22 '13 at 16:50
  • 4
    This is now officially documented under [operator matchers](https://www.relishapp.com/rspec/rspec-expectations/v/2-14/docs/built-in-matchers/operator-matchers) – Kelvin Aug 12 '13 at 15:42
  • 8
    If you're using the new "expect" syntax found in rspec 3.0, see the answer from @JoshKovach. – clozach Apr 12 '14 at 22:41
  • 57
    **Rspec 3.0** syntax is `expect([1, 2, 3]).to match_array([2, 1, 3])` see: http://stackoverflow.com/a/19436763/33226 – Gavin Miller Feb 27 '15 at 18:42
  • how do you access the error message in the case of a mismatch ? I am trying to use this to describe the mismatch between two arrays within two higher level objects – Donovan Thomson Mar 23 '15 at 10:20
  • Adding to what @GavinMiller mentioned (and as pointed out in the references SO link), `match_array` is an alias for `contain_exactly`, as seen in the [documentation](https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/contain-exactly-matcher) – Ruy Diaz Sep 25 '19 at 16:57
272

Since RSpec 2.11 you can also use match_array.

array.should match_array(another_array)

Which could be more readable in some cases.

[1, 2, 3].should =~ [2, 3, 1]
# vs
[1, 2, 3].should match_array([2, 3, 1])
Valentin Nemcev
  • 4,940
  • 2
  • 20
  • 21
  • 8
    yup, just upgraded to rails 4 and =~ stopped working where match_array works fine, thanks! – opsb Dec 06 '13 at 22:59
  • 2
    I dunno if that is more readable. Now it reads like it should be an exact match, but it isn't. The previous squiggle was vague enough to mean nothing for an array, so I didn't have the preconception. Maybe it's just me. – Hakanai Mar 13 '14 at 00:34
  • 1
    For "exact", you always have `eq()`, so I guess `match_array()` is vague enough for me. – awendt Apr 11 '14 at 10:22
  • This doesn't work for me on ordered lists. It thinks lists, which have the same items in a different order, are the same. :-( – djangofan Jul 18 '16 at 23:12
  • FWIW `match_array` is an alias for `contain_exactly` ([documentation](https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/contain-exactly-matcher)) – Ruy Diaz Sep 25 '19 at 16:59
147

I've found =~ to be unpredictable and it has failed for no apparent reason. Past 2.14, you should probably use

expect([1, 2, 3]).to match_array([2, 3, 1])
Josh Kovach
  • 7,679
  • 3
  • 45
  • 62
  • 18
    Also valid past 3.0: `expect([1, 2, 3]).to contain_exactly(2, 3, 1)`. https://www.relishapp.com/rspec/rspec-expectations/v/3-0/docs/built-in-matchers/contain-exactly-matcher – clozach Apr 22 '14 at 23:09
48

Use match_array, which takes another array as an argument, or contain_exactly, which takes each element as a separate argument, and is sometimes useful for readability. (docs)

Examples:

expect([1, 2, 3]).to match_array [3, 2, 1]

or

expect([1, 2, 3]).to contain_exactly 3, 2, 1
nicholaides
  • 19,211
  • 12
  • 66
  • 82
17

For RSpec 3 use contain_exactly:

See https://relishapp.com/rspec/rspec-expectations/v/3-2/docs/built-in-matchers/contain-exactly-matcher for details, but here's an extract:

The contain_exactly matcher provides a way to test arrays against each other in a way that disregards differences in the ordering between the actual and expected array. For example:

    expect([1, 2, 3]).to    contain_exactly(2, 3, 1) # pass
    expect([:a, :c, :b]).to contain_exactly(:a, :c ) # fail

As others have pointed out, if you want to assert the opposite, that the arrays should match both contents and order, then use eq, ie.:

    expect([1, 2, 3]).to    eq([1, 2, 3]) # pass
    expect([1, 2, 3]).to    eq([2, 3, 1]) # fail
notapatch
  • 6,569
  • 6
  • 41
  • 45
Tim Diggins
  • 4,364
  • 3
  • 30
  • 49
1

not documented very well but i added links anyways:

Rspec3 docs

expect(actual).to eq(expected)


Rspec2 docs

expect([1, 2, 3]).to match_array([2, 3, 1])

Blair Anderson
  • 19,463
  • 8
  • 77
  • 114
  • 14
    Both expect(actual).to eq(expected) and expect(actual).to match_array(expected) work in rspec3, but they are doing different things. #match_array ignores the ordering, while #eq does not. – gucki Oct 02 '15 at 19:56
  • 2
    This does not answer the question, since the OP specifically asked to disregard the order. – NobodysNightmare May 19 '16 at 14:19
  • Yes! This worked for me. A comparison that fails if the order of elements is not the same. Thank you! I refer to the `.to eq` method, not the `match_array` . – djangofan Jul 18 '16 at 23:17