Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

jestjs

jasmine

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?

like image 395
Benjamin H Boruff Avatar asked Oct 19 '16 15:10

Benjamin H Boruff


People also ask

What is deep equality in jest?

toEqual when you want to check that two objects have the same value. This matcher recursively checks the equality of all fields, rather than checking for object identity—this is also known as "deep equal". For example, toEqual and toBe behave differently in this test suite, so all the tests pass.

Which assertion used to test a value is with exact equality in jest?

Common Matchers​ The simplest way to test a value is with exact equality. expect(2 + 2).

How do you expect an array in jest?

expect. arrayContaining(array) matches a received array which contains all of the elements in the expected array. That is, the expected array is a subset of the received array. Therefore, it matches a received array which contains elements that are not in the expected array.

How to do partial matches on arrays and objects in jest?

Take your JavaScript testing to the next level by learning the ins and outs of Jest, the top JavaScript testing library. It’s possible to do partial matches on Arrays and Objects in Jest using expect.objectContaining and expect.arrayContaining. expect has some powerful matcher methods to do things like the above partial matches.

How to test for the opposite of a matcher in jest?

toEqual recursively checks every field of an object or array. You can also test for the opposite of a matcher: In tests, you sometimes need to distinguish between undefined, null, and false, but you sometimes do not want to treat these differently. Jest contains helpers that let you be explicit about what you want.

How to test on a subset of an array in jest?

Sometimes with Jest, you only need to test part of an object exists, or test on a subset of an array. We can do this using Jest’s partial matchers. In this post I will cover: Using Jest’s objectContaining to match on certain key/value pairs in objects Using Jest’s arrayContaining to match on certain values in arrays

Should you use expect or jest to write tests?

expect has some powerful matcher methods to do things like the above partial matches. Using Jest at an advanced level means using tools like these to write tests that are better isolated and less brittle (this is what I’m tryin to achieve with the Jest Handbook ).


10 Answers

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.

like image 59
biphobe Avatar answered Oct 18 '22 02:10

biphobe


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

expect(new Set(["pink wool", "diorite"])).toEqual(new Set(["diorite", "pink wool"]));
like image 27
0xcaff Avatar answered Oct 18 '22 00:10

0xcaff


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.

like image 43
nitzel Avatar answered Oct 18 '22 00:10

nitzel


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"]));
like image 31
Ulad Kasach Avatar answered Oct 18 '22 02:10

Ulad Kasach


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.

like image 35
Jay Wick Avatar answered Oct 18 '22 02:10

Jay Wick


What about checking the content and the length?

  expect(resultArray).toEqual(expect.arrayContaining(expectedArray));
  expect(resultArray.length).toEqual(expectedArray.length);
like image 24
Peter Vogel Avatar answered Oct 18 '22 00:10

Peter Vogel


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));
});
like image 26
Sergiu Mare Avatar answered Oct 18 '22 00:10

Sergiu Mare


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.

like image 4
tymzap Avatar answered Oct 18 '22 02:10

tymzap


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.

like image 1
mukesh210 Avatar answered Oct 18 '22 01:10

mukesh210


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.

like image 1
gabeodess Avatar answered Oct 18 '22 00:10

gabeodess