Match partial objects in Chai assertions?
Asked Answered
U

10

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...

Underside answered 9/4, 2015 at 8:0 Comment(1)
It's not Chai, but Jest has expect().toMatchObject(). Someone might find this useful.Plumate
S
25

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');
});
Spireme answered 3/11, 2016 at 15:3 Comment(0)
D
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');
Divert answered 8/12, 2016 at 10:13 Comment(0)
H
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");
Harlequin answered 13/5, 2020 at 5:0 Comment(9)
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.Prakash
@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.Harlequin
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.Prakash
e.g. If it ended up being refactored to expect(getC1(array)).to.include("xxx"); you have to trust 'getC1' to work.Prakash
@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.Harlequin
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 pluginsPrakash
@Pureferret no because you have to target the element with [0], that’s why my answer had more upvotes being posted 4 years later.Harlequin
@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 anymoreLiving
@Living wow.. of course abstract it into a utility function. I even edited the answer for youHarlequin
I
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'
    }
  ]);
Instantaneous answered 26/3, 2016 at 20:59 Comment(1)
LOL, so is it shallow or deep equal?Remunerative
C
2

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',
      ...
    }
  ]
  `
);
Carillon answered 16/7, 2017 at 15:45 Comment(0)
B
2

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' });
});
Burck answered 14/5, 2019 at 5:59 Comment(0)
L
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');
});
Lemkul answered 17/4, 2015 at 20:55 Comment(0)
W
1

In case, you need to use it with spy and called.with, please check this answer : https://mcmap.net/q/451816/-has-been-called-with-object-assertion

for example

expect(spy1).to.have.been.called.with.objectContaining({ a: 1 });
Wellfound answered 5/4, 2021 at 18:50 Comment(0)
C
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]);
        });
Churchwarden answered 25/6, 2018 at 6:33 Comment(0)
B
0

I know it's an old thread, but none of the plugin-less answers worked for me.

You need to use to.have.nested.property, e.g.:

expect([{ foo: "bar" }]).to.have.nested.property("[0].foo", "bar");

.deep is for deeply comparing object equality, not partial equality. So this would also validate:

expect([{ foo: "bar" }]).to.have.deep.nested.property("[0]", { foo: "bar" });
Bram answered 16/5 at 9:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.