168

With Jasmine is there a way to test if 2 arrays contain the same elements, but are not necessarily in the same order? ie

array1 = [1,2,3];
array2 = [3,2,1];

expect(array1).toEqualIgnoreOrder(array2);//should be true
David says Reinstate Monica
  • 19,209
  • 22
  • 79
  • 122

14 Answers14

109

Edit

Jasmine 2.8 adds arrayWithExactContents that will succeed if the actual value is an Array that contains all of the elements in the sample in any order.

See keksmasta's answer


Original (outdated) answer

If it's just integers or other primitive values, you can sort() them before comparing.

expect(array1.sort()).toEqual(array2.sort());

If its objects, combine it with the map() function to extract an identifier that will be compared

array1 = [{id:1}, {id:2}, {id:3}];
array2 = [{id:3}, {id:2}, {id:1}];

expect(array1.map(a => a.id).sort()).toEqual(array2.map(a => a.id).sort());
Coloured Panda
  • 3,293
  • 3
  • 20
  • 30
  • the default array sort method uses string comparison for numbers. `"10" < "2" === true` – Shmiddty May 22 '18 at 20:46
  • `[10, 2, 1].sort() ---> [1, 10, 2]` – Shmiddty May 22 '18 at 21:00
  • 14
    @Shmiddty I dont see how it matters in this case. As long as the order is the same for both arrays, it should be fine. – Coloured Panda May 23 '18 at 13:43
  • 5
    Fair point. It is worth noting that `sort` happens in-place, though. (it mutates the instance on which it is called) – Shmiddty May 23 '18 at 19:32
  • @Shmiddty that's true, but `.map` returns a new array, so you're still not mutating the original. – redbmk Jan 02 '19 at 21:04
  • 4
    The object portion of this answer will not actually verify that the objects match, since it is only comparing the mapped arrays. You don't need map, `sort` takes an optional function that it can use to do the comparison. – slifty Aug 09 '20 at 15:33
  • If for example the sorting key is "id" but there is extra data and those are not equal (but should be) this fails. – paul23 Jul 21 '21 at 16:29
  • 2
    This should not be the first answer as it's outdated. Please see answer below regarding arrayWithExactContents – Shachar Har-Shuv May 25 '22 at 21:14
  • @ShacharHar-Shuv Thank you, I added a note that a different answer is preferred now. Although I'm not sure how it works when comparing objects. – Coloured Panda Jun 27 '22 at 07:27
52

You could use expect.arrayContaining(array) from standard jest:

  const expected = ['Alice', 'Bob'];
  it('matches even if received contains additional elements', () => {
    expect(['Alice', 'Bob', 'Eve']).toEqual(expect.arrayContaining(expected));
  });
Marina
  • 855
  • 6
  • 4
  • 32
    It is not an appropriate answer for this use case as the two arrays in the question are expected to be equal, with only order differing. – mjarraya Jan 28 '21 at 12:57
  • 2
    This is the correct answer. Please mark this as the correct answer. @mjarraya just test for the length of both arrays to be the same and you are fine. – enanone May 05 '21 at 06:49
  • 5
    It should not be marked as the correct answer since it is not complete! @enanone – mjarraya May 05 '21 at 09:51
  • 3
    There are two issues: 1. length needs to be checked. expect(actual.length).toEqual(expected.length); 2. It should be jasmine.arrayContaining. https://jasmine.github.io/2.6/introduction#section-Partial_Array_Matching_with_%3Ccode%3Ejasmine.arrayContaining%3C/code%3E – zeroliu May 20 '21 at 17:52
  • 12
    ```expect([1, 2, 3]).toEqual(expect.arrayContaining([3, 3, 3]))``` will still pass, so this should not be used. – Jordan Burnett Mar 13 '22 at 13:50
51

jasmine version 2.8 and later has

jasmine.arrayWithExactContents()

Which expects that an array contains exactly the elements listed, in any order.

array1 = [1,2,3];
array2 = [3,2,1];
expect(array1).toEqual(jasmine.arrayWithExactContents(array2))

See https://jasmine.github.io/api/3.4/jasmine.html

keksmasta
  • 511
  • 4
  • 2
22

The jest-extended package provides us few assertions to simplify our tests, it's less verbose and for failing tests the error is more explicit.

For this case we could use toIncludeSameMembers

expect([{foo: "bar"}, {baz: "qux"}]).toIncludeSameMembers([{baz: "qux"}, {foo: "bar"}]);
dave008
  • 402
  • 3
  • 9
13

simple...

array1 = [1,2,3];
array2 = [3,2,1];

expect(array1).toEqual(jasmine.arrayContaining(array2));
ProfiProg
  • 171
  • 1
  • 4
10
// check if every element of array2 is element of array1
// to ensure [1, 1] !== [1, 2]
array2.forEach(x => expect(array1).toContain(x))

// check if every element of array1 is element of array2
// to ensure [1, 2] !== [1, 1]
array1.forEach(x => expect(array2).toContain(x))

// check if they have equal length to ensure [1] !== [1, 1]
expect(array1.length).toBe(array2.length)
Jannic Beck
  • 2,385
  • 21
  • 30
  • 2
    Use `.forEach` instead of `.map` to save some time and a bunch of memory. – Darkhogg Mar 19 '18 at 11:59
  • 1
    Unfortunately this will pass with the following arrays even though they are different: `array1 = [1, 2]`, `array2 = [1, 1]` – redbmk Jan 03 '19 at 17:22
  • 2
    Nice catch @redbmk I added a check for this, thanks! – Jannic Beck Jan 08 '19 at 09:04
  • I think there's still an issue - what if the arrays are `[1,1,2]` and `[1,2,2]`? Maybe using a Map for each one or something? e.g. `array1.reduce((map, item) => { map.set(item, (map.get(item) || 0) + 1)), new Map())` for both arrays, then loop through them and check that the amounts are the same? Seems like a lot of iterations but would be more thorough. – redbmk Jan 11 '19 at 21:16
  • Exclusions from the control array may be used (remove element when found, then check length is 0 in the end), but it doesn't worth the effort in regular cases. – lifecoder Jan 21 '19 at 09:37
1
//Compare arrays without order
//Example
//a1 = [1, 2, 3, 4, 5]
//a2 = [3, 2, 1, 5, 4]
//isEqual(a1, a2) -> true
//a1 = [1, 2, 3, 4, 5];
//a2 = [3, 2, 1, 5, 4, 6];
//isEqual(a1, a2) -> false


function isInArray(a, e) {
  for ( var i = a.length; i--; ) {
    if ( a[i] === e ) return true;
  }
  return false;
}

function isEqArrays(a1, a2) {
  if ( a1.length !== a2.length ) {
    return false;
  }
  for ( var i = a1.length; i--; ) {
    if ( !isInArray( a2, a1[i] ) ) {
      return false;
    }
  }
  return true;
}
Ravi
  • 31
  • 5
0

This approach has worse theoretical worst-case run-time performance, but, because it does not perform any writes on the array, it might be faster in many circumstances (haven't tested performance yet):

WARNING: As Torben pointed out in the comments, this approach only works if both arrays have unique (non-repeating) elements (just like several of the other answers here).

/**
 * Determine whether two arrays contain exactly the same elements, independent of order.
 * @see https://stackoverflow.com/questions/32103252/expect-arrays-to-be-equal-ignoring-order/48973444#48973444
 */
function cmpIgnoreOrder(a, b) {
  const { every, includes } = _;
  return a.length === b.length && every(a, v => includes(b, v));
}

// the following should be all true!
const results = [
  !!cmpIgnoreOrder([1,2,3], [3,1,2]),
  !!cmpIgnoreOrder([4,1,2,3], [3,4,1,2]),
  !!cmpIgnoreOrder([], []),
  !cmpIgnoreOrder([1,2,3], [3,4,1,2]),
  !cmpIgnoreOrder([1], []),
  !cmpIgnoreOrder([1, 3, 4], [3,4,5])
];

console.log('Results: ', results)
console.assert(_.reduce(results, (a, b) => a && b, true), 'Test did not pass!');
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.js"></script>
Domi
  • 22,151
  • 15
  • 92
  • 122
0
function equal(arr1, arr2){
    return arr1.length === arr2.length
    &&
    arr1.every((item)=>{
        return arr2.indexOf(item) >-1
    }) 
    &&
    arr2.every((item)=>{
        return arr1.indexOf(item) >-1
    })
}

The idea here is to first determine if the length of the two arrays are same, then check if all elements are in the other's array.

Lying_cat
  • 1,288
  • 2
  • 12
  • 16
0

Here's a solution that will work for any number or arrays

https://gist.github.com/tvler/cc5b2a3f01543e1658b25ca567c078e4

const areUnsortedArraysEqual = (...arrs) =>
  arrs.every((arr, i, [first]) => !i || arr.length === first.length) &&
  arrs
    .map(arr =>
      arr.reduce(
        (map, item) => map.set(item, (map.get(item) || 0) + 1),
        new Map(),
      ),
    )
    .every(
      (map, i, [first]) =>
        !i ||
        [...first, ...map].every(([item]) => first.get(item) === map.get(item)),
    );

Some tests (a few answers to this question don't account for arrays with multiple items of the same value, so [1, 2, 2] and [1, 2] would incorrectly return true)

[1, 2] true
[1, 2], [1, 2] true
[1, 2], [1, 2], [1, 2] true
[1, 2], [2, 1] true
[1, 1, 2], [1, 2, 1] true
[1, 2], [1, 2, 3] false
[1, 2, 3, 4], [1, 2, 3], [1, 2] false
[1, 2, 2], [1, 2] false
[1, 1, 2], [1, 2, 2] false
[1, 2, 3], [1, 2], [1, 2, 3] false
Tyler
  • 113
  • 1
  • 8
0

This algorithm is great for arrays where each item is unique. If not, you can add in something to check for duplicates...

tests = [
  [ [1,0,1] , [0,1,1] ],
  [ [1,0,1] , [0,0,1] ], //breaks on this one...
  [ [2,3,3] , [2,2,3] ], //breaks on this one also...
  [ [1,2,3] , [2,1,3] ],
  [ [2,3,1] , [1,2,2] ],
  [ [2,2,1] , [1,3,2] ]
]

tests.forEach(function(test) {
  console.log('eqArraySets( '+test[0]+' , '+test[1]+' ) = '+eqArraySets( test[0] , test[1] ));
});


function eqArraySets(a, b) {
 if ( a.length !== b.length ) { return false; }
 for ( var i = a.length; i--; ) {
  if ( !(b.indexOf(a[i])>-1) ) { return false; }
  if ( !(a.indexOf(b[i])>-1) ) { return false; }
 }
 return true;
}
Joe DF
  • 5,438
  • 6
  • 41
  • 63
0

There is currenly a matcher for this USE CASE:

https://github.com/jest-community/jest-extended/pull/122/files

test('passes when arrays match in a different order', () => {
  expect([1, 2, 3]).toMatchArray([3, 1, 2]);
  expect([{ foo: 'bar' }, { baz: 'qux' }]).toMatchArray([{ baz: 'qux' }, { foo: 'bar' }]);
});
0

I am currently using this helper function (for TypeScript). It makes sure that arrays that have non unique elements are supported as well.

function expectArraysToBeEqualIgnoringOrder<T>(arr1: T[], arr2: T[]) {

    while(arr1.length > 0) {

        expect(arr1.length).toEqual(arr2.length)

        const elementToDelete = arr1[0]

        arr1 = arr1.filter(element => element !== elementToDelete)
        arr2 = arr2.filter(element => element !== elementToDelete)

    }

    expect(arr2.length).toEqual(0)

}

Many of the other asnwers do not correctly handle cases like this:

array1: [a, b, b, c]
array2: [a, b, c, c]

Here the number of elements in both arrays is the same and both arrays contain all elements from the other array, yet they are different arrays and the test should fail. It runs in O(n^2) (precisely (n^2 + n) / 2), so it's not suitable for very large arrays, but it's suitable for arrays that are not easilly sorted and therefore can not be compared in O(n * log(n))

Giorgio Acquati
  • 111
  • 1
  • 7
0

This uses forEach to compare every element in each array on a unique object property (used status here).

const expected = [ 
     { count: 1, status: "A" },
     { count: 3, status: "B" },
];
result.forEach((item) => {
     expect(expected.find(a => a.status === item.status)).toEqual(item);
})
Obinna Nnenanya
  • 1,530
  • 14
  • 15