Skip to content

Commit 808f4a0

Browse files
authored
Merge pull request #146 from palmerj3/validateJunit
Update unit test suite so it validates junit output
2 parents 8557e13 + dc1e274 commit 808f4a0

File tree

5 files changed

+83
-65
lines changed

5 files changed

+83
-65
lines changed

.travis.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ node_js:
55
- "13"
66
- "14"
77
env:
8-
- JEST_VERSION=^22.0.0
9-
- JEST_VERSION=^23.0.0
108
- JEST_VERSION=^24.0.0
119
- JEST_VERSION=^25.0.0
1210
- JEST_VERSION=^26.0.0

__tests__/buildJsonResults.test.js

Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,46 @@
33
const buildJsonResults = require('../utils/buildJsonResults');
44
const constants = require('../constants/index');
55

6+
let jsonResults;
7+
let ignoreJunitErrors = false;
8+
69
describe('buildJsonResults', () => {
10+
afterEach(() => {
11+
if (ignoreJunitErrors !== true) {
12+
// Verify each tests JSON output results in a
13+
// compliant junit.xml file based on __tests__/lib/junit.xsd (jenkins xsd)
14+
expect(jsonResults).toBeCompliantJUnit();
15+
}
16+
17+
// Reset ignoreJunitErrors
18+
ignoreJunitErrors = false;
19+
jsonResults = undefined;
20+
});
21+
722
it('should contain number of tests in testSuite', () => {
823
const noFailingTestsReport = require('../__mocks__/no-failing-tests.json');
9-
const jsonResults = buildJsonResults(noFailingTestsReport, '/', constants.DEFAULT_OPTIONS);
24+
jsonResults = buildJsonResults(noFailingTestsReport, '/', constants.DEFAULT_OPTIONS);
1025

1126
expect(jsonResults.testsuites[1].testsuite[0]._attr.tests).toBe(1);
1227
});
1328

1429
it('should contain number of tests in testSuites', () => {
1530
const noFailingTestsReport = require('../__mocks__/no-failing-tests.json');
16-
const jsonResults = buildJsonResults(noFailingTestsReport, '/', constants.DEFAULT_OPTIONS);
31+
jsonResults = buildJsonResults(noFailingTestsReport, '/', constants.DEFAULT_OPTIONS);
1732

1833
expect(jsonResults.testsuites[0]._attr.tests).toBe(1);
1934
});
2035

2136
it('should return the proper name from ancestorTitles when usePathForSuiteName is "false"', () => {
2237
const noFailingTestsReport = require('../__mocks__/no-failing-tests.json');
23-
const jsonResults = buildJsonResults(noFailingTestsReport, '/', constants.DEFAULT_OPTIONS);
38+
jsonResults = buildJsonResults(noFailingTestsReport, '/', constants.DEFAULT_OPTIONS);
2439

2540
expect(jsonResults.testsuites[1].testsuite[0]._attr.name).toBe('foo');
2641
});
2742

2843
it('should return the proper filename when suiteNameTemplate is "{filename}"', () => {
2944
const noFailingTestsReport = require('../__mocks__/no-failing-tests.json');
30-
const jsonResults = buildJsonResults(noFailingTestsReport, '/',
45+
jsonResults = buildJsonResults(noFailingTestsReport, '/',
3146
Object.assign({}, constants.DEFAULT_OPTIONS, {
3247
suiteNameTemplate: "{filename}"
3348
}));
@@ -36,7 +51,7 @@ describe('buildJsonResults', () => {
3651

3752
it('should support suiteNameTemplate as function', () => {
3853
const noFailingTestsReport = require('../__mocks__/no-failing-tests.json');
39-
const jsonResults = buildJsonResults(noFailingTestsReport, '/',
54+
jsonResults = buildJsonResults(noFailingTestsReport, '/',
4055
Object.assign({}, constants.DEFAULT_OPTIONS, {
4156
suiteNameTemplate: (vars) => {
4257
return 'function called with vars: ' + Object.keys(vars).join(', ');
@@ -48,7 +63,7 @@ describe('buildJsonResults', () => {
4863

4964
it('should return the proper classname when classNameTemplate is "{classname}"', () => {
5065
const noFailingTestsReport = require('../__mocks__/no-failing-tests.json');
51-
const jsonResults = buildJsonResults(noFailingTestsReport, '/',
66+
jsonResults = buildJsonResults(noFailingTestsReport, '/',
5267
Object.assign({}, constants.DEFAULT_OPTIONS, {
5368
classNameTemplate: "{classname}"
5469
}));
@@ -58,7 +73,7 @@ describe('buildJsonResults', () => {
5873

5974
it('should return the proper title when classNameTemplate is "{title}"', () => {
6075
const noFailingTestsReport = require('../__mocks__/no-failing-tests.json');
61-
const jsonResults = buildJsonResults(noFailingTestsReport, '/',
76+
jsonResults = buildJsonResults(noFailingTestsReport, '/',
6277
Object.assign({}, constants.DEFAULT_OPTIONS, {
6378
classNameTemplate: "{title}"
6479
}));
@@ -68,7 +83,7 @@ describe('buildJsonResults', () => {
6883

6984
it('should return the proper filepath when classNameTemplate is "{filepath}"', () => {
7085
const noFailingTestsReport = require('../__mocks__/no-failing-tests.json');
71-
const jsonResults = buildJsonResults(noFailingTestsReport, '/',
86+
jsonResults = buildJsonResults(noFailingTestsReport, '/',
7287
Object.assign({}, constants.DEFAULT_OPTIONS, {
7388
classNameTemplate: "{filepath}"
7489
}));
@@ -78,7 +93,7 @@ describe('buildJsonResults', () => {
7893

7994
it('should return the proper filename when classNameTemplate is "{filename}"', () => {
8095
const noFailingTestsReport = require('../__mocks__/no-failing-tests.json');
81-
const jsonResults = buildJsonResults(noFailingTestsReport, '/',
96+
jsonResults = buildJsonResults(noFailingTestsReport, '/',
8297
Object.assign({}, constants.DEFAULT_OPTIONS, {
8398
classNameTemplate: "{filename}"
8499
}));
@@ -89,7 +104,7 @@ describe('buildJsonResults', () => {
89104
it('should return the proper displayName when classNameTemplate is {displayName}', () => {
90105
const multiProjectNoFailingTestsReport = require('../__mocks__/multi-project-no-failing-tests.json');
91106

92-
const jsonResults = buildJsonResults(multiProjectNoFailingTestsReport, '/',
107+
jsonResults = buildJsonResults(multiProjectNoFailingTestsReport, '/',
93108
Object.assign({}, constants.DEFAULT_OPTIONS, {
94109
classNameTemplate: "{displayName}"
95110
}));
@@ -99,7 +114,7 @@ describe('buildJsonResults', () => {
99114

100115
it('should return the proper suitename when classNameTemplate is "{suitename}"', () => {
101116
const noFailingTestsReport = require('../__mocks__/no-failing-tests.json');
102-
const jsonResults = buildJsonResults(noFailingTestsReport, '/',
117+
jsonResults = buildJsonResults(noFailingTestsReport, '/',
103118
Object.assign({}, constants.DEFAULT_OPTIONS, {
104119
classNameTemplate: "{suitename}"
105120
}));
@@ -109,7 +124,7 @@ describe('buildJsonResults', () => {
109124

110125
it('should support return the function result when classNameTemplate is a function', () => {
111126
const noFailingTestsReport = require('../__mocks__/no-failing-tests.json');
112-
const jsonResults = buildJsonResults(noFailingTestsReport, '/',
127+
jsonResults = buildJsonResults(noFailingTestsReport, '/',
113128
Object.assign({}, constants.DEFAULT_OPTIONS, {
114129
classNameTemplate: (vars) => {
115130
return 'function called with vars: ' + Object.keys(vars).join(', ');
@@ -121,7 +136,7 @@ describe('buildJsonResults', () => {
121136

122137
it('should return the proper filepath when titleTemplate is "{filepath}"', () => {
123138
const noFailingTestsReport = require('../__mocks__/no-failing-tests.json');
124-
const jsonResults = buildJsonResults(noFailingTestsReport, '/',
139+
jsonResults = buildJsonResults(noFailingTestsReport, '/',
125140
Object.assign({}, constants.DEFAULT_OPTIONS, {
126141
titleTemplate: "{filepath}"
127142
}));
@@ -130,7 +145,7 @@ describe('buildJsonResults', () => {
130145

131146
it('should return the proper filepath when suiteNameTemplate is "{filepath}" and usePathForSuiteName is "false"', () => {
132147
const noFailingTestsReport = require('../__mocks__/no-failing-tests.json');
133-
const jsonResults = buildJsonResults(noFailingTestsReport, '/',
148+
jsonResults = buildJsonResults(noFailingTestsReport, '/',
134149
Object.assign({}, constants.DEFAULT_OPTIONS, {
135150
suiteNameTemplate: "{filepath}"
136151
}));
@@ -139,7 +154,7 @@ describe('buildJsonResults', () => {
139154

140155
it('should return the proper name from ancestorTitles when suiteNameTemplate is set to "{title}" and usePathForSuiteName is "true"', () => {
141156
const noFailingTestsReport = require('../__mocks__/no-failing-tests.json');
142-
const jsonResults = buildJsonResults(noFailingTestsReport, '/',
157+
jsonResults = buildJsonResults(noFailingTestsReport, '/',
143158
Object.assign({}, constants.DEFAULT_OPTIONS, {
144159
usePathForSuiteName: "true"
145160
}));
@@ -148,7 +163,7 @@ describe('buildJsonResults', () => {
148163

149164
it('should return the proper name from testFilePath when usePathForSuiteName is "true"; no appDirectory set', () => {
150165
const noFailingTestsReport = require('../__mocks__/no-failing-tests.json');
151-
const jsonResults = buildJsonResults(noFailingTestsReport, '/',
166+
jsonResults = buildJsonResults(noFailingTestsReport, '/',
152167
Object.assign({}, constants.DEFAULT_OPTIONS, {
153168
usePathForSuiteName: "true"
154169
}));
@@ -157,7 +172,7 @@ describe('buildJsonResults', () => {
157172

158173
it('should return the proper name from testFilePath when usePathForSuiteName is "true"; with appDirectory set', () => {
159174
const noFailingTestsReport = require('../__mocks__/no-failing-tests.json');
160-
const jsonResults = buildJsonResults(noFailingTestsReport, '/path/to/test',
175+
jsonResults = buildJsonResults(noFailingTestsReport, '/path/to/test',
161176
Object.assign({}, constants.DEFAULT_OPTIONS, {
162177
usePathForSuiteName: "true"
163178
}));
@@ -166,14 +181,14 @@ describe('buildJsonResults', () => {
166181

167182
it('should return the proper classname when ancestorSeparator is default', () => {
168183
const noFailingTestsReport = require('../__mocks__/no-failing-tests.json');
169-
const jsonResults = buildJsonResults(noFailingTestsReport, '/',
184+
jsonResults = buildJsonResults(noFailingTestsReport, '/',
170185
Object.assign({}, constants.DEFAULT_OPTIONS));
171186
expect(jsonResults.testsuites[1].testsuite[2].testcase[0]._attr.classname).toBe('foo baz should bar');
172187
});
173188

174189
it('should return the proper classname when ancestorSeparator is customized', () => {
175190
const noFailingTestsReport = require('../__mocks__/no-failing-tests.json');
176-
const jsonResults = buildJsonResults(noFailingTestsReport, '/',
191+
jsonResults = buildJsonResults(noFailingTestsReport, '/',
177192
Object.assign({}, constants.DEFAULT_OPTIONS, {
178193
ancestorSeparator: " › "
179194
}));
@@ -182,7 +197,7 @@ describe('buildJsonResults', () => {
182197

183198
it('should parse failure messages for failing tests', () => {
184199
const failingTestsReport = require('../__mocks__/failing-tests.json');
185-
const jsonResults = buildJsonResults(failingTestsReport, '/path/to/test', constants.DEFAULT_OPTIONS);
200+
jsonResults = buildJsonResults(failingTestsReport, '/path/to/test', constants.DEFAULT_OPTIONS);
186201

187202
const failureMsg = jsonResults.testsuites[1].testsuite[2].testcase[1].failure;
188203

@@ -198,7 +213,7 @@ describe('buildJsonResults', () => {
198213
const startDate = new Date(multiProjectNoFailingTestsReport.startTime);
199214
spyOn(Date, 'now').and.returnValue(startDate.getTime() + 1234);
200215

201-
const jsonResults = buildJsonResults(multiProjectNoFailingTestsReport, '/',
216+
jsonResults = buildJsonResults(multiProjectNoFailingTestsReport, '/',
202217
Object.assign({}, constants.DEFAULT_OPTIONS, {
203218
suiteNameTemplate: "{displayName}-foo",
204219
titleTemplate: "{displayName}-foo"
@@ -209,13 +224,18 @@ describe('buildJsonResults', () => {
209224

210225
it('should not return the file name by default', () => {
211226
const noFailingTestsReport = require('../__mocks__/no-failing-tests.json');
212-
const jsonResults = buildJsonResults(noFailingTestsReport, '/', constants.DEFAULT_OPTIONS);
227+
jsonResults = buildJsonResults(noFailingTestsReport, '/', constants.DEFAULT_OPTIONS);
213228
expect(jsonResults.testsuites[1].testsuite[2].testcase[0]._attr.file).toBe(undefined);
214229
});
215230

216231
it('should return the file name when addFileAttribute is "true"', () => {
232+
// Ignore junit errors for this attribute
233+
// It is added for circle-ci and is known to not generate
234+
// jenkins-compatible junit
235+
ignoreJunitErrors = true;
236+
217237
const noFailingTestsReport = require('../__mocks__/no-failing-tests.json');
218-
const jsonResults = buildJsonResults(noFailingTestsReport, '/',
238+
jsonResults = buildJsonResults(noFailingTestsReport, '/',
219239
Object.assign({}, constants.DEFAULT_OPTIONS, {
220240
addFileAttribute: "true"
221241
}));
@@ -224,17 +244,17 @@ describe('buildJsonResults', () => {
224244

225245
it('should show output of console if includeConsoleOutput is true', () => {
226246
const reportWithConsoleOutput = require('../__mocks__/test-with-console-output.json');
227-
const jsonResults = buildJsonResults(reportWithConsoleOutput, '/',
247+
jsonResults = buildJsonResults(reportWithConsoleOutput, '/',
228248
Object.assign({}, constants.DEFAULT_OPTIONS, {
229249
includeConsoleOutput: "true"
230250
}));
231251

232-
expect(jsonResults.testsuites[1].testsuite[1]['system-out']).toBeDefined();
252+
expect(jsonResults.testsuites[1].testsuite[3]['system-out']).toBeDefined();
233253
});
234254

235255
it('should NOT show output of console if includeConsoleOutput is not set or false', () => {
236256
const reportWithConsoleOutput = require('../__mocks__/test-with-console-output.json');
237-
const jsonResults = buildJsonResults(reportWithConsoleOutput, '/',
257+
jsonResults = buildJsonResults(reportWithConsoleOutput, '/',
238258
Object.assign({}, constants.DEFAULT_OPTIONS, {
239259
includeConsoleOutput: "false"
240260
}));
@@ -244,17 +264,17 @@ describe('buildJsonResults', () => {
244264

245265
it('should show short console output if includeShortConsoleOutput is true', () => {
246266
const reportWithShortConsoleOutput = require('../__mocks__/test-with-console-output.json');
247-
const jsonResults = buildJsonResults(reportWithShortConsoleOutput, '/',
267+
jsonResults = buildJsonResults(reportWithShortConsoleOutput, '/',
248268
Object.assign({}, constants.DEFAULT_OPTIONS, {
249269
includeShortConsoleOutput: "true"
250270
}));
251271

252-
expect(jsonResults.testsuites[1].testsuite[1]['system-out']._cdata).toEqual("[\n \"I am bar\",\n \"Some output here from a lib\"\n]");
272+
expect(jsonResults.testsuites[1].testsuite[3]['system-out']._cdata).toEqual("[\n \"I am bar\",\n \"Some output here from a lib\"\n]");
253273
});
254274

255275
it('should NOT show short console output if includeShortConsoleOutput is not set or false', () => {
256276
const reportWithShortConsoleOutput = require('../__mocks__/test-with-console-output.json');
257-
const jsonResults = buildJsonResults(reportWithShortConsoleOutput, '/',
277+
jsonResults = buildJsonResults(reportWithShortConsoleOutput, '/',
258278
Object.assign({}, constants.DEFAULT_OPTIONS, {
259279
includeShortConsoleOutput: "false"
260280
}));

__tests__/lib/setupTests.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,16 @@ const schemaPath = path.join(__dirname, 'junit.xsd');
77
const schemaStr = fs.readFileSync(schemaPath);
88
const schema = libxmljs.parseXmlString(schemaStr);
99

10-
expect.extend({
11-
toConvertToXmlAndPassXsd(jsonResults) {
10+
global.expect.extend({
11+
toBeCompliantJUnit(jsonResults) {
1212
const xmlStr = xml(jsonResults, { indent: ' '});
1313

1414
const libxmljsObj = libxmljs.parseXmlString(xmlStr);
1515
const isValid = libxmljsObj.validate(schema);
1616

1717
if (!isValid) {
1818
return {
19-
message: () => libxmljsObj.validationErrors.join('\n'),
19+
message: () => `${libxmljsObj.validationErrors.join('\n')}\n${xmlStr}`,
2020
pass: false
2121
}
2222
} else {
@@ -26,4 +26,4 @@ expect.extend({
2626
}
2727
}
2828
}
29-
});
29+
});

jest.config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ module.exports = {
77
'<rootDir>/node_modules/',
88
'<rootDir>/__tests__/lib'
99
],
10-
testEnvironment: 'node'
10+
testEnvironment: 'node',
11+
setupFilesAfterEnv: ["<rootDir>/__tests__/lib/setupTests.js"]
1112
},
1213
"<rootDir>/integration-tests/testResultsProcessor/",
1314
"<rootDir>/integration-tests/reporter/"
1415
],
15-
setupFilesAfterEnv: ["<rootDir>/__tests__/lib/setupTests.js"],
1616
reporters: ['default', '.']
1717
};

utils/buildJsonResults.js

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -105,34 +105,6 @@ module.exports = function (report, appDirectory, options) {
105105
jsonResults.testsuites[0]._attr.failures += suite.numFailingTests;
106106
jsonResults.testsuites[0]._attr.tests += suiteNumTests;
107107

108-
// Write stdout console output if available
109-
if (options.includeConsoleOutput === 'true' && suite.console && suite.console.length) {
110-
// Stringify the entire console object
111-
// Easier this way because formatting in a readable way is tough with XML
112-
// And this can be parsed more easily
113-
let testSuiteConsole = {
114-
'system-out': {
115-
_cdata: JSON.stringify(suite.console, null, 2)
116-
}
117-
};
118-
119-
testSuite.testsuite.push(testSuiteConsole);
120-
}
121-
122-
// Write short stdout console output if available
123-
if (options.includeShortConsoleOutput === 'true' && suite.console && suite.console.length) {
124-
// Extract and then Stringify the console message value
125-
// Easier this way because formatting in a readable way is tough with XML
126-
// And this can be parsed more easily
127-
let testSuiteConsole = {
128-
'system-out': {
129-
_cdata: JSON.stringify(suite.console.map(item => item.message), null, 2)
130-
}
131-
};
132-
133-
testSuite.testsuite.push(testSuiteConsole);
134-
}
135-
136108
if (!ignoreSuitePropertiesCheck) {
137109
let junitSuiteProperties = require(junitSuitePropertiesFilePath)(suite);
138110

@@ -206,6 +178,34 @@ module.exports = function (report, appDirectory, options) {
206178
testSuite.testsuite.push(testCase);
207179
});
208180

181+
// Write stdout console output if available
182+
if (options.includeConsoleOutput === 'true' && suite.console && suite.console.length) {
183+
// Stringify the entire console object
184+
// Easier this way because formatting in a readable way is tough with XML
185+
// And this can be parsed more easily
186+
let testSuiteConsole = {
187+
'system-out': {
188+
_cdata: JSON.stringify(suite.console, null, 2)
189+
}
190+
};
191+
192+
testSuite.testsuite.push(testSuiteConsole);
193+
}
194+
195+
// Write short stdout console output if available
196+
if (options.includeShortConsoleOutput === 'true' && suite.console && suite.console.length) {
197+
// Extract and then Stringify the console message value
198+
// Easier this way because formatting in a readable way is tough with XML
199+
// And this can be parsed more easily
200+
let testSuiteConsole = {
201+
'system-out': {
202+
_cdata: JSON.stringify(suite.console.map(item => item.message), null, 2)
203+
}
204+
};
205+
206+
testSuite.testsuite.push(testSuiteConsole);
207+
}
208+
209209
jsonResults.testsuites.push(testSuite);
210210
});
211211

0 commit comments

Comments
 (0)