34

I am looking for the best way to match the following:

expect([
    {
        C1: 'xxx',
        C0: 'this causes it not to match.'
    }
]).to.deep.include.members([
    {
        C1: 'xxx'
    }
]);

The above doesn't work because C0 exists in the actual, but not the expected. In short, I want this expect to PASS, but I'm not sure how to do it without writing a bunch of custom code...

Fábio Nascimento
  • 2,644
  • 1
  • 21
  • 27
JayPrime2012
  • 2,672
  • 4
  • 28
  • 44

9 Answers9

24

chai-subset or chai-fuzzy might also perform what you're looking for.

Chai-subset should work like this:

expect([
  {
    C1: 'xxx',
    C0: 'this causes it not to match.'
  }
]).to.containSubset([{C1: 'xxx'}]);

Personally if I don't want to include another plugin I will use the property or keys matchers that chai includes:

([
  {
    C1: 'xxx',
    C0: 'this causes it not to match.'
  }
]).forEach(obj => {
  expect(obj).to.have.key('C1'); // or...
  expect(obj).to.have.property('C1', 'xxx');
});
thom_nic
  • 7,809
  • 6
  • 42
  • 43
5

without plugins: http://chaijs.com/api/bdd/#method_property

  expect([
    {
      C1: 'xxx',
      C0: 'this causes it not to match.'
    }
  ]).to.have.deep.property('[0].C1', 'xxx');
ni-ko-o-kin
  • 103
  • 1
  • 6
5

Clean, functional and without dependencies

simply use a map to filter the key you want to check

something like:

const array = [
    {
        C1: 'xxx',
        C0: 'this causes it not to match.'
    }
];

expect(array.map(e=>e.C1)).to.include("xxx");

https://www.chaijs.com/api/bdd/

======

Edit: For more readability, abstract it into a utility function:

// test/utils.js

export const subKey = (array, key) => array.map(e=>e[key]);

Then import it in your test, which can be read as a sentence:

expect(subKey(array,"C1")).to.include("xxx");
Sebastien Horin
  • 10,803
  • 4
  • 52
  • 54
  • this relies on your test code (the mapping function) being correct, and possibly having to test your test code.... it's not the best solution. – AncientSwordRage Nov 03 '21 at 13:14
  • 1
    @Pureferret I think we can assume the native “map” function is safe to use without testing it. At least it’s a lot less risky than using an external lib. – Sebastien Horin Nov 03 '21 at 17:41
  • I'm not doubting that the native function is safe, I'm a) highlighting that there's some modification of the test data which feels bad, and b) that you need to make sure that whatever you're calling to get the test data is always correct (i.e. it has to know about `C1`). What happens when it's hypothetically extracted to a test helper function, and it's no longer clear what it does in this test. It's the thin edge of the wedge. – AncientSwordRage Nov 03 '21 at 18:06
  • e.g. If it ended up being refactored to `expect(getC1(array)).to.include("xxx");` you have to trust 'getC1' to work. – AncientSwordRage Nov 03 '21 at 18:09
  • @Pureferret theorically yes but in real life it's overkill given you access a first level key. To avoid mistakes, you can put the key into a variable, then just call the variable later i.e. `const keyToTest = 'C1';`. Other answers here offer to install a lib, that is way more dangerous and an anti-pattern! Coding is made of compromises, so "common sense first" please. – Sebastien Horin Nov 05 '21 at 14:16
  • I'd rather trust a plugin that's been (theoretically) vetted by a community of programmers. Regardless, there's a way to do with [without plugins](https://stackoverflow.com/a/41036867/1075247) – AncientSwordRage Nov 05 '21 at 14:27
  • @Pureferret no because you have to target the element with [0], that’s why my answer had more upvotes being posted 4 years later. – Sebastien Horin Nov 05 '21 at 14:44
  • @SebastienHorin readability and simplicity of tests - are the keys. If one needs to mind-compile the test before understanding it - it is a worse test, than a straightforward one. I needed 5sec to read your test and 0sec to read the more or less standard "Chai-expression". Both solutions don't cover my case, but `to.have.deep.property(...)` is definitely more readable (and we read code more often than we write code). So, _more upvotes_ is not true anymore – maxkoryukov Dec 19 '22 at 08:07
  • @maxkoryukov wow.. of course abstract it into a utility function. I even edited the answer for you – Sebastien Horin Dec 20 '22 at 15:50
3

There are a few different chai plugins which all solve this problem. I am a fan of shallow-deep-equal. You'd use it like this:

expect([
    {
      C1: 'xxx',
      C0: 'this causes it not to match.'
    }
  ]).to.shallowDeepEqual([
    {
      C1: 'xxx'
    }
  ]);
Pete Hodgson
  • 15,644
  • 5
  • 38
  • 46
1

I believe the simplest (and certainly easiest) way would be to:

var actual=[
  {
    C1:'xxx',
    C0:'yyy'
  }
];

actual.forEach(function(obj){
  expect(obj).to.have.property('C1','xxx');
});
Rob Raisch
  • 17,040
  • 4
  • 48
  • 58
1

I wrote chai-match-pattern and lodash-match-pattern to handle partial matching (and many more) deep matching scenarios.

var chai = require('chai');
var chaiMatchPattern = require('chai-match-pattern');
chai.use(chaiMatchPattern);

// Using JDON pattern in expectation
chai.expect([
  {
    C1: 'xxx',
    C0: 'this causes it not to match.'
  }
]).to.matchPattern([
  {
    C1: 'xxx',
    '...': ''
  }
]);

// Using the slightly cleaner string pattern notation in expectation
chai.expect([
  {
    C1: 'xxx',
    C0: 'this causes it not to match.'
  }
]).to.matchPattern(`
  [
    {
      C1: 'xxx',
      ...
    }
  ]
  `
);
mjhm
  • 16,497
  • 10
  • 44
  • 55
1

You can use pick and omit underscore functions to select/reject properties to test:

const { pick, omit } = require('underscore');

const obj = {
  C1: 'xxx',
  C0: 'this causes it not to match.',
};

it('tests sparse object with pick', () => {
  expect(pick(obj, 'C1')).to.eql({ C1: 'xxx' });
});

it('tests sparse object with omit', () => {
  expect(omit(obj, 'C0')).to.eql({ C1: 'xxx' });
});
Hirurg103
  • 4,783
  • 2
  • 34
  • 50
1

In case, you need to use it with spy and called.with, please check this answer : https://stackoverflow.com/a/58940221/1151741

for example

expect(spy1).to.have.been.called.with.objectContaining({ a: 1 });
Nigrimmist
  • 10,289
  • 4
  • 52
  • 53
0

Slightly updated version of #RobRaisch because for empty comparison giving error because '' not equal ""

let expected = {
            dateOfBirth: '',
            admissionDate: '',
            dischargeDate: '',
            incidentLocation: null
        };
        Object.keys(expected).forEach(function(key) {
            expect(actual[key]).to.equal(expected[key]);
        });
Farhan
  • 1
  • 1