Chai expect: an array to contain an object with at least these properties and values
Asked Answered
C

7

22

I'm trying to validate that an array of objects like this:

[
    {
        a: 1,
        b: 2,
        c: 3
    },
    {
        a: 4,
        b: 5,
        c: 6
    },
    ...
]

contains at least one object with both { a: 1 } and { c: 3 }:

I thought I could do this with chai-things, but I don't know all the properties of the object to be able to use

expect(array).to.include.something.that.deep.equals({ ??, a: 1, c: 3});

and contain.a.thing.with.property doesn't work with multiple properties :/

What's the best way to test something like this?

Cuttlefish answered 21/12, 2015 at 15:18 Comment(0)
C
20

Most elegant solution I could come up with (with the help of lodash):

expect(_.some(array, { 'a': 1, 'c': 3 })).to.be.true;
Cuttlefish answered 23/12, 2015 at 23:43 Comment(4)
What exactly is some ?Broker
@Broker Here it is referring to some of lodash. See lodash.com/docs/#some It's also a vanilla JavaScript function that you can use to check if a list has an item with your specified constraints. Think about it as array.find except it is returning true when the callback is fulfilled instead of the item. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…Barnebas
@Barnebas thanks, I though lodash was an user in this question somewhere. :)Broker
In other words, mocha and chai don't have the functionality that OP was looking for.Delia
A
18

The desired solution seems to be something like:

expect(array).to.include.something.that.includes({a: 1, c: 3});

I.e. array contains an item which includes those properties. Unfortunately, it appears to not be supported by chai-things at the moment. For the foreseeable future.

After a number of different attempts, I've found that converting the original array makes the task easier. This should work without additional libraries:

// Get items that interest us/remove items that don't.
const simplifiedArray = array.map(x => ({a: x.a, c: x.c}));
// Now we can do a simple comparison.
expect(simplifiedArray).to.deep.include({a: 1, c: 3});

This also allows you to check for several objects at the same time (my use case).

expect(simplifiedArray).to.include.deep.members([{
  a: 1,
  c: 3
}, {
  a: 3,
  c: 5
}]);
Attempt answered 7/9, 2017 at 23:39 Comment(2)
Fine, it works! But how to invert it to assert that element NOT in array?Motet
@Motet Assuming you mean object, does adding not not do what you want? If you actually mean array, I think you're looking for a different question entirely.Attempt
C
4

Most straight forward way I can think of is:

const chai = require('chai');
const expect = chai.expect;

chai.use(require('chai-like'));
chai.use(require('chai-things'));

expect([
  {
    a: 1,
    b: 2,
    c: 3,
  },
  {
    a: 4,
    b: 5,
    c: 6,
  },
]).to.be.an('array').that.contains.something.like({ a: 1, c: 3 });
Cog answered 5/7, 2020 at 17:27 Comment(1)
Note that chai-like and chai-things have to be loaded in that order. Otherwise you'll get odd assertions like expected { id: 3 } to be like { id: 3 }.Horseradish
B
4

Solution without third libraries or plugins:

let array = [
    { a: 1, b: 2, c: 3 },
    { a: 4, b: 5, c: 6 }
];

let match = array.map(({a, c}) => ({a, c})).to.deep.include({ a:1, c:3});
Brower answered 3/12, 2021 at 9:55 Comment(0)
U
3

Seems like the chai-subset plugin from chai seems to have done the trick. Here is something I have working:

const chai = require('chai');
const chaiSubset = require('chai-subset');
chai.use(chaiSubset);
const expect = chai.expect;

 expect([ { type: 'text',
    id: 'name',
    name: 'name',
    placeholder: 'John Smith',
    required: 'required' },
  { type: 'email',
    id: 'email',
    name: 'email',
    placeholder: '[email protected]',
    required: 'required' },
  { type: 'submit' } ]).containSubset([{ type: 'text', type: 'email', type: 'submit' }]);
Unilocular answered 12/3, 2018 at 18:37 Comment(1)
I don't think this is a correct test. If you have an object with multiple keys with the same name, the last one will win. So { type: 'text', type: 'email', type: 'submit' } is actually the same as {type: 'submit'}Ploughboy
L
2

Found the simplest solution without external dependencies:

expect(
    [
        { a: 1, b: 2, c: 3 },
        { a: 4, b: 5, c: 6 },
    ].map(({a,b}) => { return {a,b} })
).to.include.deep.members([
    { a: 1, b: 2 },
]);
Lowery answered 3/6, 2023 at 1:21 Comment(0)
D
0

You could write your own function to test the array. In this example you pass in the array and the object containing the relevant key/value pairs:

function deepContains(arr, search) {

  // first grab the keys from the search object
  // we'll use the length later to check to see if we can
  // break out of the loop
  var searchKeys = Object.keys(search);

  // loop over the array, setting our check variable to 0
  for (var check = 0, i = 0; i < arr.length; i++) {
    var el = arr[i], keys = Object.keys(el);

    // loop over each array object's keys
    for (var ii = 0; ii < keys.length; ii++) {
      var key = keys[ii];

      // if there is a corresponding key/value pair in the
      // search object increment the check variable
      if (search[key] && search[key] === el[key]) check++;
    }

    // if we have found an object that contains a match
    // for the search object return from the function, otherwise
    // iterate again
    if (check === searchKeys.length) return true;
  }
  return false;
}

deepContains(data, { a: 4, c: 6 }); // true
deepContains(data, { a: 1, c: 6 }); // false

DEMO

Dewitt answered 21/12, 2015 at 16:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.