10

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.

Community
  • 1
  • 1
GolezTrol
  • 114,394
  • 18
  • 182
  • 210
  • This is similar to the `PropTypes.shape` function of React. Also check out https://github.com/sashafklein/shape – d4nyll Jul 14 '17 at 11:54

2 Answers2

12

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);
GolezTrol
  • 114,394
  • 18
  • 182
  • 210
5

Correct me if I understood wrong but the following will work with plain chai.

expect({foo: {bar: 10}}).to.have.deep.property('foo.bar', 10); // Success
expect({foo: {bar: 11}}).to.have.deep.property('foo.bar', 10); // Fail
expect({foo: {qux: 20}}).to.have.deep.property('foo.bar', 10); // Fail
expect({baz: 'a', foo: {bar: 10, baz: 20}}).to.have.deep.property('foo.bar', 10); // Success
x_maras
  • 2,177
  • 1
  • 25
  • 34
  • 1
    Thanks for thinking with me. Your code is testing for a single property at a time. I recognized that would work well, but I want to have a more easy way of checking a more complex option. Foo and bar are of course an example, my actual object is a little more complex, and I'd have to test (in this case alone) for about a dozen of properties on multiple levels. – GolezTrol Jan 30 '16 at 13:21
  • Then indeed my answer was not correct. I also didn't know chai-subset, I just checked it out from your answer. Good to know. I usually take the stupid way and check things property by property, but I also like my tests to be simple and stupid :) – x_maras Jan 30 '16 at 13:25
  • 1
    @x_maras - the first and the last examples don't work in chai 4.1.2. Didn't bother test the others. – Roobie Oct 10 '17 at 03:32
  • 1
    @Roobie This answer was from almost 2 years (January/2016) ago and as you can see the comments there was no issue with running the code back then. I haven't used chai the last year, but I just noticed that version 4 of chai was released May 2017. In their release notes they specify that they changed this functionality to be strict. Check the 3rd bullet of the release notes. They replaced this with nested. https://github.com/chaijs/chai/releases/tag/4.0.0 – x_maras Oct 10 '17 at 07:36
  • Dot notation doesn't work anymore without `nested` class. So it should be like this: `.to.have.deep.nested.property` – Murat Colyaran Apr 12 '22 at 09:15