giodamelio
giodamelio

Reputation: 5605

Using Jest property matchers on arrays of objects

I am attempting to use Jest's new Property Matcher feature (since Jest 23.0.0) to match on an array of objects that contain a generated field. I have tried putting both a plain object and a matcher definition using expect.arrayContaining and expect.objectContaining like I might when matching manually. Is there any way to do this currently?

const sportsBallPeople = [
  {
    createdAt: new Date(),
    name: 'That one famous guy from Cleveland'
  },
  {
    createdAt: new Date(),
    name: 'That tall guy'
  }
];
expect(sportsBallPeople).toMatchSnapshot(<something goes here>);

Upvotes: 37

Views: 66824

Answers (5)

Mehari
Mehari

Reputation: 3247

toMatchObject works for an array too because arrays are objects

expect(receivedArray).toMatchObject(expectedArray)

Upvotes: 5

Tom
Tom

Reputation: 4836

Create an array of Property Matcher (using Array(n).fill(matcher) for example), of the same size as the result object you want to match (n=sportsBallPeople.length). matcher representing here the Property Matcher of one item of your array.

That way:

  • It will check each element of the array with the property matcher.
  • It will create only one snapshot with the full array.
  • If the result is not the same size as the last snapshot, the test will fail because the snapshot will be different. So it will fail even if the new result is bigger

(others answers may not fail when the array grow if they create one snapshot per item, as new snapshot are usually silently created in CI and doesn't trigger a test failure)


const sportsBallPeople = [
  {
    createdAt: new Date(),
    name: 'That one famous guy from Cleveland'
  },
  {
    createdAt: new Date(),
    name: 'That tall guy'
  }
];

const itemMatcher = {
    createdAt: expect.any(Date),
}

const arrayMatcher = Array(sportsBallPeople.length).fill(itemMatcher)

expect(sportsBallPeople).toMatchSnapshot(arrayMatcher);

or, simply:

expect(sportsBallPeople).toMatchSnapshot(Array(sportsBallPeople.length).fill({
    createdAt: expect.any(Date),
}));

Resulting snapshot will be:

exports[`snapshot 1`] = `
Array [
  Object {
    "createdAt": Any<Date>,
    "name": "That one famous guy from Cleveland",
  },
  Object {
    "createdAt": Any<Date>,
    "name": "That tall guy",
  },
]`

Upvotes: 5

Ian Bunag
Ian Bunag

Reputation: 31

To apply snapshot property matcher to each array entry without creating a separate assertion for each, we may create an array with length equal to the value's length filled with the matcher definition:

it('should return first 10 notes by default', async () => {
  const noteMatcher = {
    createdAt: expect.any(String),
    updatedAt: expect.any(String),
  }
  const response = await app.inject({
    method: 'GET',
    url: `/examples/users/1/notes`,
  })
  const payload = response.json()
  const { length } = payload.data

  expect(response.statusCode).toMatchInlineSnapshot(`200`)
  expect(length).toBe(10)
  expect(payload).toMatchSnapshot({
    data: new Array(length).fill(noteMatcher),
  })
})

Will result in the following snapshot:

exports[`should return first 10 notes by default 2`] = `
Object {
  "data": Array [
    Object {
      "createdAt": Any<String>,
      "deletedAt": null,
      "id": 1,
      "note": "Note 1",
      "title": "Title 1",
      "updatedAt": Any<String>,
      "userID": 1,
    },
    Object {
      "createdAt": Any<String>,
      "deletedAt": null,
      "id": 2,
      "note": "Note 2",
      "title": "Title 2",
      "updatedAt": Any<String>,
      "userID": 1,
    },
    // 8 omitted entries
  ],
  "success": true,
}
`;

Upvotes: 3

cramhead
cramhead

Reputation: 1067

Thanks for the tips. Often, being a test you can control the inputs making something like the following viable.

describe.only('Something', () => {
  it.only('should do something', () => {
    const x = {
      a: false,
      b: true,
      c: 157286400,
    };
    const results = functionBeingTesting(x, 84);
    expect(results[0]).toMatchInlineSnapshot({
        createdAt: expect.any(Number),
        updatedAt: expect.any(Number)
      },
      `
        Object {
          "createdAt": Any<Number>,
          "a": false,
          "b": true,
          "updatedAt": Any<Number>,
          "value": "0",
        }
      `,
    );
    expect(results[1]).toMatchInlineSnapshot({
        createdAt: expect.any(Number),
        updatedAt: expect.any(Number)
      },
      `
        Object {
          "createdAt": Any<Number>,
          "a": false,
          "b": true,
          "updatedAt": Any<Number>,
          "value": "1",
        }
      `,
    );
    expect(results[2]).toMatchInlineSnapshot({
        createdAt: expect.any(Number),
        updatedAt: expect.any(Number)
      },
      `
        Object {
          "createdAt": Any<Number>,
          "a": false,
          "b": true,
          "updatedAt": Any<Number>,
          "value": "1",
        }
      `,
    );
  });
});

Upvotes: -1

Brian Adams
Brian Adams

Reputation: 45810

Version Info

As is noted in the question, property matchers were introduced in Jest 23.0.0. Note that apps bootstrapped with create-react-app as of today (Aug 5, 2018) are still < 23.

OBJECT

Here is an example using a property matcher for a single object:

test('sportsBallPerson', () => {
  expect(sportsBallPeople[0]).toMatchSnapshot({
    createdAt: expect.any(Date)
  })
});

The snapshot generated:

exports[`sportsBallPerson 1`] = `
Object {
  "createdAt": Any<Date>,
  "name": "That one famous guy from Cleveland",
}
`;

This will correctly match createdAt to any date and the name to exactly "That one famous guy from Cleveland".

ARRAY

To test an array of objects using property matchers use forEach to loop over the array and snapshot test each object individually:

test('sportsBallPeople', () => {
  sportsBallPeople.forEach((sportsBallPerson) => {
    expect(sportsBallPerson).toMatchSnapshot({
      createdAt: expect.any(Date)
    });
  });
});

The snapshots generated:

exports[`sportsBallPeople 1`] = `
Object {
  "createdAt": Any<Date>,
  "name": "That one famous guy from Cleveland",
}
`;

exports[`sportsBallPeople 2`] = `
Object {
  "createdAt": Any<Date>,
  "name": "That tall guy",
}
`;

forEach ensures that the objects are tested in order, and each object is properly snapshot tested as described above.

Additional Info

It is interesting to note that directly testing an array using property matchers does not work properly and has unexpected side-effects.

My first attempt to test an array was to create the following test:

test('sportsBallPeople as array', () => {
  expect(sportsBallPeople).toMatchSnapshot([
    { createdAt: expect.any(Date) },
    { createdAt: expect.any(Date) }
  ]);
});

It generated the following snapshot:

exports[`sportsBallPeople as array 1`] = `
Array [
  Object {
    "createdAt": Any<Date>,
  },
  Object {
    "createdAt": Any<Date>,
  },
]
`;

This is incorrect since the name properties are missing, but the test still passes (Jest v23.4.2). The test passes even if the names are changed and additional properties are added.

Even more interesting was that as soon as this test executed, any following tests using property matchers were adversely affected. For example, placing this test ahead of the the test looping over the objects changed those snapshots to the following:

exports[`sportsBallPeople 1`] = `
Object {
  "createdAt": Any<Date>,
}
`;

exports[`sportsBallPeople 2`] = `
Object {
  "createdAt": Any<Date>,
}
`;

In summary, directly passing an array to use with property matchers does not work and can negatively affect other snapshot tests using property matchers.

Upvotes: 49

Related Questions