mochajs / mocha

☕️ simple, flexible, fun javascript test framework for node.js & the browser
https://mochajs.org
MIT License
22.53k stars 3k forks source link

🐛 Bug: Calling `done` with circular reference object results in type error converting JSON instead of correct failure message #2598

Open hrdwdmrbl opened 7 years ago

hrdwdmrbl commented 7 years ago

Just spent way too long getting this bug and not knowing why. After figuring it out, here's the simplest repro I could make

it('Mocha is confused', function(done) {
  new Promise(function(resolve, reject) {
    var a = {};
    a.b = a;
    resolve(a);
  }).then(done).catch(done);
});

Produces

  1) Mocha is confused

  0 passing (20ms)
  1 failing

  1)  Mocha is confused:
     TypeError: Converting circular structure to JSON
      at Object.stringify (native)

The problem here is that there isn't a stack-trace, so I start off with absolutely no idea how to solve the problem. I have no idea why Mocha wants to stringify what I give to the done function, but it does...

ScottFreeCode commented 7 years ago

done() is success (e.g. .then(function(){done()}), although if you're doing this you probably have a case for returning the promise instead of using done); done(<anything>) is failure, so it's trying to process the "failure" and is running into a circular reference in it. This could be simplified to:

it("circular done", function(done) {
  var x = {}
  x.y = x
  done(x)
})

We have another bug open, #2433, about circular errors or some such thing causing problems for the JSON reporter, but in this case the reporter doesn't seem to make a difference. This may also suggest an issue that some things are being done with the "failure" value before the point where Mocha is supposed to check that it's an Error (normally Mocha requires failures to be Errors and should give you a warning if you try to use something else, such as a raw string).

Notably, this does not happen if I throw or return a rejected promise instead of calling done:

it("circular throw", function() {
  var x = {}
  x.y = x
  throw x
})
it("circular promise", function() {
  var x = {}
  x.y = x
  return Promise.reject(x)
})

Error: the object { "y": [Circular] } was thrown, throw an Error :)