Skip to content

Commit

Permalink
Merge pull request #247 from eps1lon/feat/testcase-properties
Browse files Browse the repository at this point in the history
feat: Allow adding `<properties>` to `<testcase>`
  • Loading branch information
palmerj3 committed Apr 15, 2023
2 parents 64b7cf0 + 5775c62 commit c753b42
Show file tree
Hide file tree
Showing 6 changed files with 244 additions and 7 deletions.
37 changes: 33 additions & 4 deletions README.md
Expand Up @@ -75,7 +75,9 @@ Reporter options should also be strings exception for suiteNameTemplate, classNa
| `JEST_JUNIT_REPORT_TEST_SUITE_ERRORS` | `reportTestSuiteErrors` | Reports test suites that failed to execute altogether as `error`. _Note:_ since the suite name cannot be determined from files that fail to load, it will default to file path.| `false` | N/A
| `JEST_JUNIT_NO_STACK_TRACE` | `noStackTrace` | Omit stack traces from test failure reports, similar to `jest --noStackTrace` | `false` | N/A
| `JEST_USE_PATH_FOR_SUITE_NAME` | `usePathForSuiteName` | **DEPRECATED. Use `suiteNameTemplate` instead.** Use file path as the `name` attribute of `<testsuite>` | `"false"` | N/A
| `JEST_JUNIT_TEST_SUITE_PROPERTIES_JSON_FILE` | `testSuitePropertiesFile` | Name of the custom testsuite properties file | `"junitProperties.js"` | N/A
| `JEST_JUNIT_TEST_CASE_PROPERTIES_JSON_FILE` | `testCasePropertiesFile` | Name of the custom testcase properties file | `"junitProperties.js"` | N/A
| `JEST_JUNIT_TEST_CASE_PROPERTIES_DIR` | `testCasePropertiesDirectory` | Location of the custom testcase properties file | `process.cwd()` | N/A
| `JEST_JUNIT_TEST_SUITE_PROPERTIES_JSON_FILE` | `testSuitePropertiesFile` | Name of the custom testsuite properties file | `"junitTestCaseProperties.js"` | N/A
| `JEST_JUNIT_TEST_SUITE_PROPERTIES_DIR` | `testSuitePropertiesDirectory` | Location of the custom testsuite properties file | `process.cwd()` | N/A


Expand Down Expand Up @@ -236,9 +238,9 @@ Create a file in your project root directory named junitProperties.js:
```js
module.exports = () => {
return {
key: "value"
}
});
key: "value",
};
};
```

Will render
Expand All @@ -254,4 +256,31 @@ Will render
</testsuites>
```

#### Adding custom testcase properties

Create a file in your project root directory named junitTestCaseProperties.js:
```js
module.exports = (testResult) => {
return {
"dd_tags[test.invocations]": testResult.invocations,
};
};
```

Will render
```xml
<testsuites name="jest tests">
<testsuite name="addition" tests="1" errors="0" failures="0" skipped="0" timestamp="2017-07-13T09:42:28" time="0.161">
<testcase classname="addition positive numbers should add up" name="addition positive numbers should add up" time="0.004">
<properties>
<property name="dd_tags[test.invocations]" value="1" />
</properties>
</testcase>
</testsuite>
</testsuites>
```

WARNING: Properties for testcases is not following standard JUnit XML schema.
However, other consumers may support properties for testcases like [DataDog metadata through `<property>` elements](https://docs.datadoghq.com/continuous_integration/tests/junit_upload/?tab=jenkins#providing-metadata-through-property-elements)

[test-results-processor]: https://github.com/jest-community/jest-junit/discussions/158#discussioncomment-392985
66 changes: 66 additions & 0 deletions __mocks__/retried-tests.json
@@ -0,0 +1,66 @@
{
"numFailedTestSuites": 0,
"numFailedTests": 0,
"numPassedTestSuites": 1,
"numPassedTests": 1,
"numPendingTestSuites": 0,
"numPendingTests": 0,
"numRuntimeErrorTestSuites": 0,
"numTotalTestSuites": 1,
"numTotalTests": 1,
"snapshot": {
"added": 0,
"failure": false,
"filesAdded": 0,
"filesRemoved": 0,
"filesUnmatched": 0,
"filesUpdated": 0,
"matched": 0,
"total": 0,
"unchecked": 0,
"unmatched": 0,
"updated": 0
},
"startTime": 1489712747092,
"success": true,
"testResults": [
{
"console": null,
"failureMessage": null,
"numFailingTests": 0,
"numPassingTests": 1,
"numPendingTests": 0,
"perfStats": {
"end": 1489712747644,
"start": 1489712747524
},
"snapshot": {
"added": 0,
"fileDeleted": false,
"matched": 0,
"unchecked": 0,
"unmatched": 0,
"updated": 0
},
"testFilePath": "/path/to/test/__tests__/foo.test.js",
"testResults": [
{
"ancestorTitles": [
"foo",
"baz"
],
"duration": 1,
"failureMessages": [],
"fullName": "foo baz should bar",
"numPassingAsserts": 0,
"status": "passed",
"title": "should bar",
"invocations": 2,
"retryReasons": ["error"]
}
],
"skipped": false
}
],
"wasInterrupted": false
}
80 changes: 80 additions & 0 deletions __tests__/buildJsonResults.test.js
Expand Up @@ -439,4 +439,84 @@ describe('buildJsonResults', () => {

expect(jsonResults.testsuites[1].testsuite[2]['system-out']).not.toBeDefined();
});

it("should add properties to testcase (non standard)", () => {
const retriedTestsReport = require("../__mocks__/retried-tests.json");
// <properties> in <testcase> is not compatible JUnit but can be consumed by some e.g. DataDog
ignoreJunitErrors = true;
// Mock Date.now() to return a fixed later value
const startDate = new Date(retriedTestsReport.startTime);
jest.spyOn(Date, 'now').mockImplementation(() => startDate.getTime() + 1234);

jsonResults = buildJsonResults(retriedTestsReport, "/", {
...constants.DEFAULT_OPTIONS,
testCasePropertiesFile: "junitDataDogInvocationsProperties.js",
});

expect(jsonResults).toMatchInlineSnapshot(`
Object {
"testsuites": Array [
Object {
"_attr": Object {
"errors": 0,
"failures": 0,
"name": "jest tests",
"tests": 1,
"time": 1.234,
},
},
Object {
"testsuite": Array [
Object {
"_attr": Object {
"errors": 0,
"failures": 0,
"name": "foo",
"skipped": 0,
"tests": 1,
"time": 0.12,
"timestamp": "2017-03-17T01:05:47",
},
},
Object {
"properties": Array [
Object {
"property": Object {
"_attr": Object {
"name": "best-tester",
"value": "Jason Palmer",
},
},
},
],
},
Object {
"testcase": Array [
Object {
"_attr": Object {
"classname": "foo baz should bar",
"name": "foo baz should bar",
"time": 0.001,
},
},
Object {
"properties": Array [
Object {
"property": Object {
"_attr": Object {
"name": "dd_tags[test.invocations]",
"value": 2,
},
},
},
],
},
],
},
],
},
],
}
`)
});
});
4 changes: 4 additions & 0 deletions constants/index.js
Expand Up @@ -18,6 +18,8 @@ module.exports = {
JEST_JUNIT_REPORT_TEST_SUITE_ERRORS: 'reportTestSuiteErrors',
JEST_JUNIT_NO_STACK_TRACE: "noStackTrace",
JEST_USE_PATH_FOR_SUITE_NAME: 'usePathForSuiteName',
JEST_JUNIT_TEST_CASE_PROPERTIES_JSON_FILE: 'testCasePropertiesFile',
JEST_JUNIT_TEST_CASE_PROPERTIES_DIR: 'testCasePropertiesDirectory',
JEST_JUNIT_TEST_SUITE_PROPERTIES_JSON_FILE: 'testSuitePropertiesFile',
JEST_JUNIT_TEST_SUITE_PROPERTIES_DIR: 'testSuitePropertiesDirectory',
},
Expand All @@ -37,6 +39,8 @@ module.exports = {
includeShortConsoleOutput: 'false',
reportTestSuiteErrors: 'false',
noStackTrace: 'false',
testCasePropertiesFile: 'junitTestCaseProperties.js',
testCasePropertiesDirectory: process.cwd(),
testSuitePropertiesFile: 'junitProperties.js',
testSuitePropertiesDirectory: process.cwd(),
},
Expand Down
5 changes: 5 additions & 0 deletions junitDataDogInvocationsProperties.js
@@ -0,0 +1,5 @@
module.exports = (testResult) => {
return {
"dd_tags[test.invocations]": testResult.invocations,
};
};
59 changes: 56 additions & 3 deletions utils/buildJsonResults.js
Expand Up @@ -5,6 +5,7 @@ const constants = require('../constants/index');
const path = require('path');
const fs = require('fs');
const getTestSuitePropertiesPath = require('./getTestSuitePropertiesPath');
const replaceRootDirInOutput = require('./getOptions').replaceRootDirInOutput;

// Wrap the varName with template tags
const toTemplateTag = function (varName) {
Expand Down Expand Up @@ -38,7 +39,21 @@ const executionTime = function (startTime, endTime) {
return (endTime - startTime) / 1000;
}

const generateTestCase = function(junitOptions, suiteOptions, tc, filepath, filename, suiteTitle, displayName){
const getTestCasePropertiesPath = (options, rootDir = null) => {
const testCasePropertiesPath = replaceRootDirInOutput(
rootDir,
path.join(
options.testCasePropertiesDirectory,
options.testCasePropertiesFile,
),
);

return path.isAbsolute(testCasePropertiesPath)
? testCasePropertiesPath
: path.resolve(testCasePropertiesPath);
};

const generateTestCase = function(junitOptions, suiteOptions, tc, filepath, filename, suiteTitle, displayName, getGetCaseProperties){
const classname = tc.ancestorTitles.join(suiteOptions.ancestorSeparator);
const testTitle = tc.title;

Expand Down Expand Up @@ -87,6 +102,30 @@ const generateTestCase = function(junitOptions, suiteOptions, tc, filepath, file
});
}

if (getGetCaseProperties !== null) {
let junitCaseProperties = getGetCaseProperties(tc);

// Add any test suite properties
let testCasePropertyMain = {
'properties': []
};

Object.keys(junitCaseProperties).forEach((p) => {
let testSuiteProperty = {
'property': {
_attr: {
name: p,
value: junitCaseProperties[p]
}
}
};

testCasePropertyMain.properties.push(testSuiteProperty);
});

testCase.testcase.push(testCasePropertyMain);
}

return testCase;
}

Expand Down Expand Up @@ -115,6 +154,9 @@ module.exports = function (report, appDirectory, options, rootDir = null) {
);
let ignoreSuitePropertiesCheck = !fs.existsSync(junitSuitePropertiesFilePath);

const testCasePropertiesPath = getTestCasePropertiesPath(options, rootDir)
const getTestCaseProperties = fs.existsSync(testCasePropertiesPath) ? require(testCasePropertiesPath) : null

// If the usePathForSuiteName option is true and the
// suiteNameTemplate value is set to the default, overrides
// the suiteNameTemplate.
Expand Down Expand Up @@ -223,7 +265,16 @@ module.exports = function (report, appDirectory, options, rootDir = null) {

// Iterate through test cases
suite.testResults.forEach((tc) => {
const testCase = generateTestCase(options, suiteOptions, tc, filepath, filename, suiteTitle, displayName)
const testCase = generateTestCase(
options,
suiteOptions,
tc,
filepath,
filename,
suiteTitle,
displayName,
getTestCaseProperties
);
testSuite.testsuite.push(testCase);
});

Expand All @@ -237,6 +288,7 @@ module.exports = function (report, appDirectory, options, rootDir = null) {
title: "Test execution failure: could be caused by test hooks like 'afterAll'.",
ancestorTitles: [""],
duration: 0,
invocations: 1,
};
const testCase = generateTestCase(
options,
Expand All @@ -245,7 +297,8 @@ module.exports = function (report, appDirectory, options, rootDir = null) {
filepath,
filename,
suiteTitle,
displayName
displayName,
getTestCaseProperties
);
testSuite.testsuite.push(testCase);
}
Expand Down

0 comments on commit c753b42

Please sign in to comment.