How to have mocha show entire object in diff on assertion error?
Asked Answered
I

4

18

I have a unit test test case created with mocha and chai's expect where I deeply compare an array of value objects to the parsed content of a JSON file.

My record object has about 20 properties, and currently only the price can cause a mismatch. On a diff, I only see five of them.

 expect(records).to.deep.equal(expected);

       "data": {
  -      "price": 3578
  +      "price": 3438
         "not_important": "foo"
         "also_not_important": "bar"
       }
       "data": {
  -      "price": 1828
  +      "price": 1698
         "not_important": "foo"
         "also_not_important": "bar"
       }

This is useful default in most cases, yet in this one it obfuscates which specific data object is breaking the assertions, as I only see redundant data here.

Assume there is a important property in the data object that would make it quite clear what expectation is breaking the test. Therefore, I would like to be able to either configure what properties are shown or to display the entire object in the diff.

How can I configure mocha's diff display?


Here is a contrived meta-syntactic example showcasing the problem:

import {expect} from "chai";

describe(("diff problem"), () => {
    it("should show case that the diff is not shown properly", () => {
        const actual = {
            a: 1,
            troiz: 0,
            bar: 0,
            baz: 2,
            poit: 3,
            narf: 4,
            fizzbuzz: 117,
            fizz: 5,
            buzz: 4,
            waldo: 115,
            mos: 85465,
            important: "THIS IS IMPORTANT",
        };

        const expected = {
            ...actual,
            a: 0,
        };

        return expect(actual).to.deep.equal(expected);
    });
});

The output of that testcase will be:

2) SourceParser diff problem should show entire diff on error of one property:

  AssertionError: expected { Object (a, troiz, ...) } to deeply equal { Object (a, troiz, ...) }
  + expected - actual

   {
  -  "a": 1
  +  "a": 0
     "bar": 0
     "baz": 2
     "buzz": 4
     "fizz": 5

Yet it would be helpful to see: important: "THIS IS IMPORTANT" as well.


Here is the modified example for the array case:

describe(("diff problem with an array"), () => {
    it("should show case that the diff is not shown properly for deep equal of arrays", () => {
        const anEntity = {
            a: 1,
            troiz: 0,
            bar: 0,
            baz: 2,
            poit: 3,
            narf: 4,
            fizzbuzz: 117,
            fizz: 5,
            buzz: 4,
            waldo: 115,
            mos: 85465,
            important: "IMPORTANT", // assume that each item has a unique important property, which is why it's helpful for it to be shown
        };

        const offendingItem = {
            ...anEntity,
            a: 0,
        };

        const actual = [
            anEntity,
            offendingItem,
            anEntity,
        ];

        const expected = [
            anEntity,
            anEntity,
            anEntity,
        ];

        return expect(actual).to.deep.equal(expected);
    });

The output will be:

  AssertionError: expected [ Array(3) ] to deeply equal [ Array(3) ]
  + expected - actual

       "troiz": 0
       "waldo": 115
     }
     {
  -    "a": 0
  +    "a": 1
       "bar": 0
       "baz": 2
       "buzz": 4
       "fizz": 5

and it won't get better with Louis' answer modifying chai as it only dumps the entire actual array first and then shows the non-helpful diff:

AssertionError: expected [ { a: 1,
    troiz: 0,
    bar: 0,
    baz: 2,
    poit: 3,
    narf: 4,
    fizzbuzz: 117,
    fizz: 5,
    buzz: 4,
    waldo: 115,
    mos: 85465,
    important: 'IMPORTANT' },
  { a: 0,
    troiz: 0,
    bar: 0,
    baz: 2,
    poit: 3,
    narf: 4,
    fizzbuzz: 117,
    fizz: 5,
    buzz: 4,
    waldo: 115,
    mos: 85465,
    important: 'IMPORTANT' },
  { a: 1,
    troiz: 0,
    bar: 0,
    baz: 2,
    poit: 3,
    narf: 4,
    fizzbuzz: 117,
    fizz: 5,
    buzz: 4,
    waldo: 115,
    mos: 85465,
    important: 'IMPORTANT' } ] to deeply equal [ { a: 1,
    troiz: 0,
    bar: 0,
    baz: 2,
    poit: 3,
    narf: 4,
    fizzbuzz: 117,
    fizz: 5,
    buzz: 4,
    waldo: 115,
    mos: 85465,
    important: 'IMPORTANT' },
  { a: 1,
    troiz: 0,
    bar: 0,
    baz: 2,
    poit: 3,
    narf: 4,
    fizzbuzz: 117,
    fizz: 5,
    buzz: 4,
    waldo: 115,
    mos: 85465,
    important: 'IMPORTANT' },
  { a: 1,
    troiz: 0,
    bar: 0,
    baz: 2,
    poit: 3,
    narf: 4,
    fizzbuzz: 117,
    fizz: 5,
    buzz: 4,
    waldo: 115,
    mos: 85465,
    important: 'IMPORTANT' } ]
      + expected - actual

           "troiz": 0
           "waldo": 115
         }
         {
      -    "a": 0
      +    "a": 1
           "bar": 0
           "baz": 2
           "buzz": 4
           "fizz": 5
Ingathering answered 25/8, 2017 at 12:48 Comment(1)
@Louis It shouldn't matter though as chai is appearantly not taking care of the diffIngathering
R
10

You can add --inline-diffs to the mocha command, which will show the entire object with line numbers and inline diffs:

mocha --inline-diffs YourSpec.js

The documentation is a bit misleading: https://mochajs.org/#diffs

The mocha result

Rahn answered 30/7, 2019 at 5:21 Comment(1)
When running mocha as library (like in Postman tests) - is it still possible?Jezabelle
S
13

As far as I know, there's no built-in way to get Chai or Mocha to produce diffs that would add to the context provided by the diff some fields that are not responsible for the test failure. And I'm not aware of any extension that would exactly what you are looking for. So I only know of workarounds.


If you set truncateThreshold configuration setting to a larger value, or to 0 if you don't want any truncation, the failure message that appears before the diff will show whole objects. So if I add this to your code:

chai.config.truncateThreshold = 0; // 0 means "don't truncate, ever".

(The configuration options are covered on this documentation page.)

Then the error I get is:

      AssertionError: expected { a: 1,
  troiz: 0,
  bar: 0,
  baz: 2,
  poit: 3,
  narf: 4,
  fizzbuzz: 117,
  fizz: 5,
  buzz: 4,
  waldo: 115,
  mos: 85465,
  important: 'THIS IS IMPORTANT' } to deeply equal { a: 0,
  troiz: 0,
  bar: 0,
  baz: 2,
  poit: 3,
  narf: 4,
  fizzbuzz: 117,
  fizz: 5,
  buzz: 4,
  waldo: 115,
  mos: 85465,
  important: 'THIS IS IMPORTANT' }
      + expected - actual

       {
      -  "a": 1
      +  "a": 0
         "bar": 0
         "baz": 2
         "buzz": 4
         "fizz": 5

Moreover, a way to get custom information in the error message is to set a custom message with the assertion, for instance:

expect(actual).to.deep.equal(
            expected,
            `failed equality test on object with important field set to: ${actual.important}`)

The custom message can be as verbose or terse as you need it to be.

If there is only one field that matters to you for differentiating objects, a custom message may be sufficient to get the information you need to track the problem.


For arrays of objects, you could perform a comparison by iterating through the actual and expected arrays and comparing each member. It would then essentially work like described above. For sure, this has disadvantages: if items 1 and 10 are different, for instance, you'll get a report only for item 1 because the test will end with that comparison failure. When you fix this problem and run the test again, you'll get a report for item 10. Whether or not in practice this is a major problem depends the kind of data you are testing.


One thing I've done in cases where the default diff algorithm was not doing what I wanted was to import a diffing library that I configured to my liking, and then use the library to perform diffs between objects that I cared about and combine the results into a final report that is then checked with an assertion.

Again, I'm not aware of a library that would do specifically what you are looking for. However, I could imagine iterating through the actual and expected arrays to produce one diff report per pair and then combining them into a larger report that contains identifying information.

Stupe answered 25/8, 2017 at 13:11 Comment(1)
Thank you very much, after looking the documentation: chaijs.com/guide/styles/#configuration I solved the issue.Helaina
R
10

You can add --inline-diffs to the mocha command, which will show the entire object with line numbers and inline diffs:

mocha --inline-diffs YourSpec.js

The documentation is a bit misleading: https://mochajs.org/#diffs

The mocha result

Rahn answered 30/7, 2019 at 5:21 Comment(1)
When running mocha as library (like in Postman tests) - is it still possible?Jezabelle
S
0

For anyone struggling with the issue when running mocha tests in a browser (regular or headless). A simple fix could get the needed diffs explicitly printed next to the error while focussing on actual differences. This saves time for debugging the errors:

import chai from 'chai'

function chaiExtensions(chai, {flag}) {
  const {assert} = chai.Assertion.prototype

  chai.Assertion.prototype.assert = function (...args) {
    try {
      assert.apply(this, args)
    } catch (e) {
      if (e.operator === 'deepStrictEqual' && e.showDiff) {
        e.message = `\n${prettyjson.render(
          deepDiff(e.actual, e.expected)
        )}\n\n${e.message}`
      }
      throw e
    }
  }
}

chai.use(specHelper.chaiExtensions)

Where diff resolver func and pretty print could be done of your choice. In my case:

deepDiff: https://gist.github.com/Yimiprod/7ee176597fef230d1451?permalink_comment_id=4757803#gistcomment-4757803

prettyjson: https://www.npmjs.com/package/prettyjson

Surra answered 26/3, 2024 at 18:53 Comment(0)
I
-6

Note due to all the downvotes: I am the OP. My intended solution is not addressing the actual issue of my architecture decision. Having a solution that compares huge objects is a code smell. I have refactored my code base so that I am not in need of this attempted solution anymore.


You are trying to solve the wrong problem. Your intended solutions is only a workaround.

Your actual problem is to rely on on deep comparing a huge array of huge objects to begin with.

You should refactor your codebase to allow price calculation through a price calculation service. Unit testing that service is straightforward with simple asserts.

Ingathering answered 21/3, 2018 at 13:14 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.