Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow writing JSON reporter to write directly to file #3255

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ yarn.lock
*_REMOTE_*
docs/_site
docs/_dist
output.json.tmp
2 changes: 2 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -1100,6 +1100,8 @@ The "JSON" reporter outputs a single large JSON object when the tests have compl

![json reporter](images/reporter-json.png?withoutEnlargement&resize=920,9999){:class="screenshot"}

By default, it will output to the console. To write directly to a file, use `--reporter-options output=filename.json`.

### JSON Stream

The "JSON stream" reporter outputs newline-delimited JSON "events" as they occur, beginning with a "start" event, followed by test passes or failures, and then the final "end" event.
Expand Down
32 changes: 30 additions & 2 deletions lib/reporters/json.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
*/

var Base = require('./base');
var fs = require('fs');
var mkdirp = require('mkdirp');
var path = require('path');

/**
* Expose `JSON`.
Expand All @@ -18,7 +21,7 @@ exports = module.exports = JSONReporter;
* @api public
* @param {Runner} runner
*/
function JSONReporter (runner) {
function JSONReporter (runner, options) {
Base.call(this, runner);

var self = this;
Expand All @@ -27,6 +30,16 @@ function JSONReporter (runner) {
var failures = [];
var passes = [];

if (options && options.reporterOptions) {
if (options.reporterOptions.output) {
if (!fs.writeFileSync) {
throw new Error('file output not supported in browser');
}
mkdirp.sync(path.dirname(options.reporterOptions.output));
self.fileName = path.resolve(options.reporterOptions.output);
}
}

runner.on('test end', function (test) {
tests.push(test);
});
Expand Down Expand Up @@ -54,7 +67,7 @@ function JSONReporter (runner) {

runner.testResults = obj;

process.stdout.write(JSON.stringify(obj, null, 2));
self.write(JSON.stringify(obj, null, 2));
});
}

Expand Down Expand Up @@ -90,3 +103,18 @@ function errorJSON (err) {
}, err);
return res;
}

/**
* Write out the given data.
*
* @param {string} data
*/
JSONReporter.prototype.write = function (data) {
if (this.fileName) {
fs.writeFileSync(this.fileName, data);
} else if (typeof process === 'object' && process.stdout) {
process.stdout.write(data);
} else {
console.log(data);
}
};
1 change: 0 additions & 1 deletion lib/reporters/xunit.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ exports = module.exports = XUnit;
*/
function XUnit (runner, options) {
Base.call(this, runner);

var stats = this.stats;
var tests = [];
var self = this;
Expand Down
237 changes: 200 additions & 37 deletions test/reporters/json.spec.js
Original file line number Diff line number Diff line change
@@ -1,63 +1,226 @@
'use strict';

var fs = require('fs');
var path = require('path');
var assert = require('assert');
var Mocha = require('../../');
var Suite = Mocha.Suite;
var Runner = Mocha.Runner;
var Test = Mocha.Test;

var TEMP_FILE_PATH = path.resolve('output.json.tmp');

describe('json reporter', function () {
var suite, runner;

beforeEach(function () {
var mocha = new Mocha({
reporter: 'json'
});
suite = new Suite('JSON suite', 'root');
runner = new Runner(suite);
/* eslint no-unused-vars: off */
var mochaReporter = new mocha._reporter(runner);
after(function () {
if (fs.existsSync(TEMP_FILE_PATH)) {
fs.unlinkSync(TEMP_FILE_PATH);
}
});

it('should have 1 test failure', function (done) {
var testTitle = 'json test 1';
var error = { message: 'oh shit' };
describe('with no output specified', function () {
beforeEach(function () {
var mocha = new Mocha({
reporter: 'json'
});
suite = new Suite('JSON suite', 'root');
runner = new Runner(suite);
/* eslint no-unused-vars: off */
var mochaReporter = new mocha._reporter(runner);
});
it('should have 1 test failure', function (done) {
var testTitle = 'json test 1';
var error = { message: 'oh shit' };

suite.addTest(new Test(testTitle, function (done) {
done(new Error(error.message));
}));
suite.addTest(new Test(testTitle, function (done) {
done(new Error(error.message));
}));

runner.run(function (failureCount) {
expect(failureCount).to.be(1);
expect(runner).to.have.property('testResults');
expect(runner.testResults).to.have.property('failures');
expect(runner.testResults.failures).to.be.an('array');
expect(runner.testResults.failures).to.have.length(1);
runner.run(function (failureCount) {
expect(failureCount).to.be(1);
expect(runner).to.have.property('testResults');
expect(runner.testResults).to.have.property('failures');
expect(runner.testResults.failures).to.be.an('array');
expect(runner.testResults.failures).to.have.length(1);

var failure = runner.testResults.failures[0];
expect(failure).to.have.property('title', testTitle);
expect(failure.err.message).to.equal(error.message);
expect(failure).to.have.property('err');
var failure = runner.testResults.failures[0];
expect(failure).to.have.property('title', testTitle);
expect(failure.err.message).to.equal(error.message);
expect(failure).to.have.property('err');

done();
done();
});
});

it('should have 1 test pending', function (done) {
var testTitle = 'json test 1';

suite.addTest(new Test(testTitle));

runner.run(function (failureCount) {
expect(failureCount).to.be(0);
expect(runner).to.have.property('testResults');
expect(runner.testResults).to.have.property('pending');
expect(runner.testResults.pending).to.be.an('array');
expect(runner.testResults.pending).to.have.length(1);

var pending = runner.testResults.pending[0];
expect(pending).to.have.property('title', testTitle);

done();
});
});
});

it('should have 1 test pending', function (done) {
var testTitle = 'json test 1';
describe('with output specified', function () {
beforeEach(function () {
var mocha = new Mocha({
reporter: 'json'
});
suite = new Suite('JSON suite', 'root');
runner = new Runner(suite);
/* eslint no-unused-vars: off */
var mochaReporter = new mocha._reporter(runner, { reporterOptions: { output: TEMP_FILE_PATH } });
});

it('should have 1 test failure', function (done) {
var testTitle = 'json test 1';
var error = { message: 'oh shit' };

suite.addTest(new Test(testTitle, function (done) {
done(new Error(error.message));
}));

runner.run(function (failureCount) {
expect(failureCount).to.be(1);
expect(runner).to.have.property('testResults');
expect(runner.testResults).to.have.property('failures');
expect(runner.testResults.failures).to.be.an('array');
expect(runner.testResults.failures).to.have.length(1);

var failure = runner.testResults.failures[0];
expect(failure).to.have.property('title', testTitle);
expect(failure.err.message).to.equal(error.message);
expect(failure).to.have.property('err');

var fileContents;
try {
fileContents = JSON.parse(fs.readFileSync(TEMP_FILE_PATH, 'utf-8'));
} catch (e) {
done(e);
return;
}

assert.deepEqual(fileContents, {
stats: {
suites: 1,
tests: 1,
passes: 0,
pending: 0,
failures: 1,
start: fileContents.stats.start,
end: fileContents.stats.end,
duration: fileContents.stats.duration
},
tests: [{
title: 'json test 1',
fullTitle: 'JSON suite json test 1',
duration: fileContents.tests[0].duration,
currentRetry: 0,
err: {
stack: fileContents.tests[0].err.stack,
message: 'oh shit'
}
}],
pending: [],
failures: [{
title: 'json test 1',
fullTitle: 'JSON suite json test 1',
duration: fileContents.failures[0].duration,
currentRetry: 0,
err: {
stack: fileContents.failures[0].err.stack,
message: 'oh shit'
}
}],
passes: []
});
assert.equal(Date.now() - new Date(fileContents.stats.start).getTime() < 10000, true);
assert.equal(Date.now() - new Date(fileContents.stats.end).getTime() < 10000, true);
assert.equal(typeof fileContents.stats.duration, 'number', JSON.stringify({ fileContents }, null, 2));
assert.equal(fileContents.stats.duration <= 30, true, JSON.stringify({ fileContents }, null, 2));
assert.equal(typeof fileContents.tests[0].duration, 'number', JSON.stringify({ fileContents }, null, 2));
assert.equal(fileContents.tests[0].duration <= 30, true, JSON.stringify({ fileContents }, null, 2));
assert.equal(typeof fileContents.failures[0].duration, 'number', JSON.stringify({ fileContents }, null, 2));
assert.equal(fileContents.failures[0].duration <= 30, true, JSON.stringify({ fileContents }, null, 2));
var errLines = fileContents.tests[0].err.stack.split('\n');
assert.equal(errLines[0], 'Error: oh shit', JSON.stringify({ fileContents }, null, 2));
var regexResult = /^ {4}at ((Object)|(Context))\.<anonymous> \(test(\/|\\)reporters(\/|\\)json\.spec\.js:92:14\)$/g.exec(errLines[1]);
assert.equal(!!regexResult, true, JSON.stringify({ regexResult, fileContents, line: errLines[1] }, null, 2));
errLines = fileContents.failures[0].err.stack.split('\n');
assert.equal(errLines[0], 'Error: oh shit', JSON.stringify({ fileContents }, null, 2));
regexResult = /^ {4}at ((Object)|(Context))\.<anonymous> \(test(\/|\\)reporters(\/|\\)json\.spec\.js:92:14\)$/g.exec(errLines[1]);
assert.equal(!!regexResult, true, JSON.stringify({ regexResult, fileContents, line: errLines[1] }, null, 2));

done();
});
});

it('should have 1 test pending', function (done) {
var testTitle = 'json test 1';

suite.addTest(new Test(testTitle));

runner.run(function (failureCount) {
expect(failureCount).to.be(0);
expect(runner).to.have.property('testResults');
expect(runner.testResults).to.have.property('pending');
expect(runner.testResults.pending).to.be.an('array');
expect(runner.testResults.pending).to.have.length(1);

suite.addTest(new Test(testTitle));
var pending = runner.testResults.pending[0];
expect(pending).to.have.property('title', testTitle);

runner.run(function (failureCount) {
expect(failureCount).to.be(0);
expect(runner).to.have.property('testResults');
expect(runner.testResults).to.have.property('pending');
expect(runner.testResults.pending).to.be.an('array');
expect(runner.testResults.pending).to.have.length(1);
var fileContents;
try {
fileContents = JSON.parse(fs.readFileSync(TEMP_FILE_PATH, 'utf-8'));
} catch (e) {
done(e);
return;
}

var pending = runner.testResults.pending[0];
expect(pending).to.have.property('title', testTitle);
assert.deepEqual(fileContents, {
stats: {
suites: 1,
tests: 1,
passes: 0,
pending: 1,
failures: 0,
start: fileContents.stats.start,
end: fileContents.stats.end,
duration: fileContents.stats.duration
},
tests: [{
title: 'json test 1',
fullTitle: 'JSON suite json test 1',
currentRetry: 0,
err: {}
}],
pending: [{
title: 'json test 1',
fullTitle: 'JSON suite json test 1',
currentRetry: 0,
err: {}
}],
failures: [],
passes: []
});
assert.equal(Date.now() - new Date(fileContents.stats.start).getTime() < 10000, true);
assert.equal(Date.now() - new Date(fileContents.stats.end).getTime() < 10000, true);

done();
done();
});
});
});
});