Is there an Array equality match function that ignores element position in jest.js?
Asked Answered
A

11

203

I get that .toEqual() checks equality of all fields for plain objects:

expect(
    {"key1":"pink  wool","key2":"diorite"}
).toEqual(
    {"key2":"diorite","key1":"pink wool"}
);

So this passes.

But the same is not true for arrays:

expect(["pink wool", "diorite"]).toEqual(["diorite", "pink wool"]);

There does not seem to be a matcher function that does this in the jest docs, i.e. that tests for the equality of two arrays irrespective of their elements positions. Do I have to test each element in one array against all the elements in the other and vice versa? Or is there another way?

Accretion answered 19/10, 2016 at 15:26 Comment(5)
Still an isue, maybe we can make a PR for Jest?Beker
Just replying here in case it gets someone else to re-read their own faulty code, but my issue (which seemed like the above), was actually causing a test to unexpectantly pass because I was doing expect( ACTUAL ).toEqual( ACTUAL ) instead of expect( EXPECTED ).toEqual( ACTUAL ). >_<Goatee
I'm confused why this question has the Jasmine tag.Toulouse
@DanielKaplan, good question. I have no idea. I don't recall after 6 years (and I don't do frontend anymore :), so I removed it! Thanks!Accretion
@BenjaminHBoruff Thanks I appreciate that. I can certainly relate, too :)Toulouse
P
215

There is no built-in method to compare arrays without comparing the order, but you can simply sort the arrays using .sort() before making a comparison:

expect(["ping wool", "diorite"].sort()).toEqual(["diorite", "pink wool"].sort());

You can check the example in this fiddle.

Percept answered 21/10, 2016 at 8:57 Comment(4)
this will not work if you are working with array of objects. Because the object would be converted to [Object object] and won't be sorted. And your comparison may still fail. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/….Fellmonger
yeah, I mean you have to provide a suitable comparator to the sort function according to the data type.Dineen
Wow, so looks like Jest is smart enough to do an item-by-item comparison?Antitrust
I suggest using toStrictEqual over toEqual where possible. It is a deeper equality check. jestjs.io/docs/expect#tostrictequalvalue Note: using toBe is even deeper (exact objects), but won't work here.Permenter
R
58

As already mentioned expect.arrayContaining checks if the actual array contains the expected array as a subset. To check for equivalence one may

  • either assert that the length of both arrays is the same (but that wouldn't result in a helpful failure message)
  • or assert the reverse: That the expected array contains the actual array:
// This is TypeScript, but remove the types and you get JavaScript
const expectArrayEquivalence = <T>(actual: T[], expected: T[]) => {
  expect(actual).toEqual(expect.arrayContaining(expected));
  expect(expected).toEqual(expect.arrayContaining(actual));
};

This still has the problem that when the test fails in the first assertion one is only made aware of the elements missing from actual and not of the extra ones that are not in expected.

Receive answered 9/8, 2019 at 11:15 Comment(0)
E
37

Put the elements into a set. Jest knows how to match these.

expect(new Set(["pink wool", "diorite"])).toEqual(new Set(["diorite", "pink wool"]));
Euridice answered 18/11, 2018 at 6:22 Comment(5)
This was introduced in Jest 23.0.0, so won't work with previous versions.Eristic
This will ignore duplicate values, which is probably not what you want. ["a", "a", "b"] will match ["a", "b"].Ojeda
Do not use this as duplicate elements are not counted. Avoid before you break your headRepp
I would highly encourage you to delete this answer as it is in almost every way inferior to the accepted. The only time I could imagine your answer to be better is a case where we have a lot of duplicates and therefore the comparison afterwards could be faster. If that is what you want to say please make it more clear. If not your answer is very misleading and can cause a lot of damage in an application as it looks fine and will work until it doesn't anymore then the damage can be dramatic.Preoccupancy
While this answer has caveats, it was exactly what I was looking for since I was trying to figure out if jest knows how to match two sets.Sere
M
21

this does not answer the question exactly, but still may help people that end up here by google search:

if you only care that a subset of the array has certain elements, use expect.arrayContaining() https://jestjs.io/docs/en/expect#expectarraycontainingarray

e.g.,

expect(["ping wool", "diorite"])
  .toEqual(expect.arrayContaining(["diorite", "pink wool"]));
Megrim answered 27/9, 2018 at 19:59 Comment(3)
This will not fail if arrays have additional values. Point was to check of equality.Mata
You can always test the length of the array to ensure that this test matches what is expected rather than a subset of what is expected.Polyploid
@Polyploid That's a good idea if the elements are guaranted to be unique. In cases like ['alice', 'alice', 'bob', 'carol'] and ['alice', 'bob', 'carol', 'carol'] checking equility of the array's length wouldn't be enough.Herndon
B
18

Another way is to use the custom matcher .toIncludeSameMembers() from jest-community/jest-extended.

Example given from the README

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

It might not make sense to import a library just for one matcher but they have a lot of other useful matchers I've find useful.

Berstine answered 16/1, 2020 at 0:43 Comment(1)
Thanks. This is probably the most clear and efficient approach. Don't know why there are so little upvotes.Schott
I
10

What about checking the content and the length?

  expect(resultArray).toEqual(expect.arrayContaining(expectedArray));
  expect(resultArray.length).toEqual(expectedArray.length);
Incivility answered 27/8, 2021 at 9:13 Comment(2)
this will do the correct assertion however the error message will be messed up. something like: expected 2 to equal 1 and nothing else.Abana
toHaveLength would improve the error messageHuddle
P
5

You can combine using sets as stated in this answer with checking length of actual result and expectation. This will ignore element position and protect you from duplicated elements in the same time.

const materials = ['pink wool', 'diorite'];
const expectedMaterials = ['diorite', 'pink wool'];

expect(new Set(materials)).toEqual(new Set(expectedMaterials));
expect(materials.length).toBe(expectedMaterials.length);

EDIT: As there is suggested in comment below, this will only work for arrays with unique values.

Pastelki answered 4/5, 2020 at 21:27 Comment(1)
be aware that this solution will work only for arrays with unique values. Try to compare ['pink wool', 'diorite', 'diorite'] with ['pink wool', 'pink wool', 'diorite']Flavorsome
E
5

If you want to compare two arrays in JEST use the bellow model.

Official link: https://jestjs.io/docs/en/expect#expectarraycontainingarray

const array1 = ['a', 'b', 'c'];
const array2 = ['a', 'b', 'c'];
const array3 = ['a', 'b'];


it("test two arrays, this will be true", () => { 
    expect(array1).toEqual(expect.arrayContaining(array2));
});

it("test two arrays, this will be false", () => { 
    expect(array3).toEqual(expect.arrayContaining(array1));
});
Eileneeilis answered 10/2, 2021 at 14:47 Comment(1)
Be careful as this - as the name suggests - checks if the array contains the items in the other array. The test will pass if you check if expect(['a','b']).toEqual(expect.arrayContaining(['a'])); as mentioned in other answers.Adverb
B
4

You can use jest toContainEqual to check if an array contains an element. Then just do that for each element in your expected array:

const actual = [{ foobar: 'C' }, { foo: 'A' }, { bar: 'B' }];
const expected = [{ foo: 'A' }, { bar: 'B' }, { foobar: 'C' }];

expect(actual).toContainEqual(expected[0]);
expect(actual).toContainEqual(expected[1]);
expect(actual).toContainEqual(expected[2]);

(Or put the expect statement in a loop if you have too many elements to check)

Bedside answered 9/11, 2022 at 8:22 Comment(0)
H
1

If you don't have array of objects, then you can simply use sort() function for sorting before comparison.(mentioned in accepted answer):

expect(["ping wool", "diorite"].sort()).toEqual(["diorite", "pink wool"].sort());

However, problem arises if you have array of objects in which case sort function won't work. In this case, you need to provide custom sorting function. Example:

const x = [
{key: 'forecast', visible: true},
{key: 'pForecast', visible: false},
{key: 'effForecast', visible: true},
{key: 'effRegForecast', visible: true}
]

// In my use case, i wanted to sort by key
const sortByKey = (a, b) => { 
  if(a.key < b.key) return -1; 
  else if(a.key > b.key) return 1; 
  else return 0; 
  }

x.sort(sortByKey)
console.log(x)

Hope it helps someone someday.

Hoopoe answered 13/3, 2020 at 7:20 Comment(2)
more compact way: const sortByKey = (a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0)Ostentation
@DavideRossetto compact? we use bundlers to transform out code to the shortest possible ;)Forjudge
I
1

Still a work in progress, but this should work albeit, the error messages may not be clear:

expect.extend({
  arrayContainingExactly(receivedOriginal, expected) {
    const received = [...receivedOriginal];

    if (received.length !== expected.length) return {
      message: () => `Expected array of length ${expected.length} but got an array of length ${received.length}`,
      pass: false,
    };

    const pass = expected.every((expectedItem, index) => {
      const receivedIndex = findIndex(received, receivedItem => {
          if (expectedItem.asymmetricMatch) return expectedItem.asymmetricMatch(receivedItem);
          return isEqual(expectedItem, receivedItem);
      });
      if (receivedIndex === -1) return false;
      received.splice(receivedIndex, 1);
      return true;
    });

    return {
      message: () => 'Success',
      pass,
    }
  }
});

Then use it like this:

expect(['foo', 'bar']).arrayContainingExactly(['foo']) // This should fail

or

expect({foo: ['foo', 'bar']}).toEqual({
  foo: expect.arrayContainingExactly(['bar', 'foo'])
}) // This should pass

We are looping through each value and removing it from the received array so that we can take advantage of the asymmetric matching provided by Jest. If we just wanted to do direct equivalency this could be simplified to just compare the 2 sorted arrays.

Note: This solution uses findIndex and isEqual from lodash.

Immobility answered 17/2, 2021 at 22:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.