Chai assertion testing whether object structure contains at least other object structure

GolezTrol picture GolezTrol · Jan 30, 2016 · Viewed 7.8k times · Source

I'm using Mocha for unit testing and Chai for assertions.

I'd like to find an easy to use solution to check if an object has the structure and properties as defined in my comparison object. But I don't need the objects to be completely equal. The subject under test should contain at least all the properties in my test object, but it might also contain other properties which are not under test at that moment.

So, I want to test a unit to check if the object it returns has at least a property named 'foo', which itself is an object that at least contains the property 'bar' with value 10. So, I have this expected outcome to test against:

var expected = {foo: {bar: 10}};

I call my unit and have my test subject in a variable sut:

var sut = MyUnit.myFunction();

So for various suts, I expect these outcomes:

// Success. Exact match
{foo: {bar: 10}}

// Fail. Structure is ok, but property value is wrong.
{foo: {bar: 11}}

// Fail. property bar is missing.
{foo: {qux: 20}}

// Success. All properties match. Extra properties (baz) in sut are ignored:
{baz: 'a', foo: {bar: 10, baz: 20}}

And then I want to compare it in a convenient way. I could test all properties individually or split it in a number of tests, but I hoped I could do something like

sut.should.deep.contain.all(expected);

However, I got the following surprising result, even if the objects are exactly the same:

AssertionError: expected { foo: { bar: 10 } } to have a property 'foo' of { bar: 10 }, but got { bar: 10 }

Of course I tried this, and a couple of other variations. The simplest testing for equality, doesn't work if the object contains extra properties.

sut.should.eql(expected);

AssertionError: expected { foo: { bar: 10, qux: 20 } } to deeply equal { foo: { bar: 10 } }

I've tested other combinations of have and contains together with deep, any or all, but none satisfied my wish.

I found the duplicate question "Chai deep contains assertion on nested objects", but the only (downvoted) answer doesn't make sense. It calls deep.eql which is redundant, and above that it just doesn't work, because it tests for strict equality.

I do know I can test all properties separately, but I would be nice to have a readable, single-statement way to test if one object is 'a subset of' the other.

Update: I ended up using the Shallow Deep Equal plugin for Chai.

Answer

GolezTrol picture GolezTrol · Jan 30, 2016

There are a couple of plugins for Chai which can solve this issue.

chai-subset

There is this subset plugin for Chai, that should be able to do this.

I'm using Mocha in the browser, but although it should be browser compatible, I haven't got this plugin working yet.

Anyway, this library contains the generic answer to the question, and after including it, the following line should work:

sut.should.containSubset(expected);

chai-shallow-deep-equal

chai-subset seemed to lack the version that is required to run it in the browser, so I went on looking for plugins. Another one that I found is chai-shallow-deep-equal.

This plugin can be used fine in the browser too. Had it up and running in seconds after downloading it from Git and using the description on the plugin page, resulting in:

sut.should.shallowDeepEqual(expected);

It will now nicely ignore extra properties in sut, but it will also give nice assertions when properties as missing or different in expected. You'll get a message like this:

AssertionError: Expected to have "2" but got "20" at path "/foo/qux".

It doesn't show all assertions, though. If you have two errors in the object, you'll only get one (the first) assertion error. To me that's not really an issue, but it could be confusing, because it might look like a fix you made introduced a new issue while it was already there.

chai-fuzzy

I've not tried chai-fuzzy, (GitHub) myself yet, but it seems it can solve the same problem too and its repository also contains the browser compatible version of the plug-in. However, it also requires yet another library, Underscore, which seems a bit overkill for the purpose. It's syntax would look like this:

sut.should.be.like(expected);