diff --git a/lib/reporters/json.js b/lib/reporters/json.js index 259a782121..ed8fc70c1f 100644 --- a/lib/reporters/json.js +++ b/lib/reporters/json.js @@ -67,17 +67,43 @@ function JSONReporter (runner) { * @return {Object} */ function clean (test) { + var err = test.err || {}; + if (err instanceof Error) { + err = errorJSON(err); + } + return { title: test.title, fullTitle: test.fullTitle(), duration: test.duration, currentRetry: test.currentRetry(), - err: errorJSON(test.err || {}) + err: cleanCycles(err) }; } +/** + * Replaces any circular references inside `obj` with '[object Object]' + * + * @api private + * @param {Object} obj + * @return {Object} + */ +function cleanCycles (obj) { + var cache = []; + return JSON.parse(JSON.stringify(obj, function (key, value) { + if (typeof value === 'object' && value !== null) { + if (cache.indexOf(value) !== -1) { + // Instead of going in a circle, we'll print [object Object] + return '' + value; + } + cache.push(value); + } + + return value; + })); +} /** - * Transform `error` into a JSON object. + * Transform an Error object into a JSON object. * * @api private * @param {Error} err diff --git a/mocha.js b/mocha.js index 16d5619e27..176ed9d413 100644 --- a/mocha.js +++ b/mocha.js @@ -3074,17 +3074,43 @@ function JSONReporter (runner) { * @return {Object} */ function clean (test) { + var err = test.err || {}; + if (err instanceof Error) { + err = errorJSON(err); + } + return { title: test.title, fullTitle: test.fullTitle(), duration: test.duration, currentRetry: test.currentRetry(), - err: errorJSON(test.err || {}) + err: cleanCycles(err) }; } +/** + * Replaces any circular references inside `obj` with '[object Object]' + * + * @api private + * @param {Object} obj + * @return {Object} + */ +function cleanCycles (obj) { + var cache = []; + return JSON.parse(JSON.stringify(obj, function (key, value) { + if (typeof value === 'object' && value !== null) { + if (cache.indexOf(value) !== -1) { + // Instead of going in a circle, we'll print [object Object] + return '' + value; + } + cache.push(value); + } + + return value; + })); +} /** - * Transform `error` into a JSON object. + * Transform an Error object into a JSON object. * * @api private * @param {Error} err diff --git a/test/reporters/json.spec.js b/test/reporters/json.spec.js index 74151fdc8b..ff36269765 100644 --- a/test/reporters/json.spec.js +++ b/test/reporters/json.spec.js @@ -60,4 +60,31 @@ describe('json reporter', function () { done(); }); }); + + it('should handle circular objects in errors', function (done) { + var testTitle = 'json test 1'; + function CircleError () { + this.message = 'oh shit'; + this.circular = this; + } + var error = new CircleError(); + + suite.addTest(new Test(testTitle, function (done) { + throw error; + })); + + runner.run(function (failureCount) { + failureCount.should.be.exactly(1); + runner.should.have.property('testResults'); + runner.testResults.should.have.property('failures'); + runner.testResults.failures.should.be.an.instanceOf(Array); + runner.testResults.failures.should.have.a.lengthOf(1); + + var failure = runner.testResults.failures[0]; + failure.should.have.property('title', testTitle); + failure.should.have.properties('err'); + + done(); + }); + }); });