jest-junit converts a Jest test run into JUnit XML that TestCollab can read. Add a
TestCollab case marker such as [TC-42] to each Jest test title, generate the report,
and upload it with @testcollab/cli.
If you have not used the CLI before, begin with TestCollab CLI: Install and First Run.
Prerequisites
- A JavaScript or TypeScript project that already runs tests with Jest.
- A TestCollab project and either an existing test plan ID or the project, CI tag, and assignee IDs needed to create a plan.
-
A TestCollab API token in
TESTCOLLAB_TOKEN. You can create one with Getting an API Token generated. - TestCollab case IDs added to the Jest test titles you want to report.
Install jest-junit
Install the reporter as a development dependency with npm or Yarn:
npm install --save-dev jest-junit
# Or with Yarn
yarn add --dev jest-junit
Configure the Jest JUnit reporter
Add jest-junit beside Jest's default reporter. The titleTemplate below
writes the original Jest test title into the JUnit <testcase name> attribute,
so the [TC-42] marker survives unchanged.
// jest.config.js
module.exports = {
reporters: [
'default',
['jest-junit', {
outputDirectory: './reports',
outputName: 'junit.xml',
titleTemplate: '{title}'
}]
]
};
If your Jest configuration is in package.json, use the same values:
{
"jest": {
"reporters": [
"default",
[
"jest-junit",
{
"outputDirectory": "./reports",
"outputName": "junit.xml",
"titleTemplate": "{title}"
}
]
]
}
}
Add TestCollab IDs to Jest test titles
Use a visible, explicit marker in each test or it title. The number is
the TestCollab test case ID.
describe('Authentication', () => {
test('[TC-42] customer can sign in with valid credentials', async () => {
const response = await signIn('customer@example.com', 'valid-password');
expect(response.status).toBe(200);
});
test('[TC-43] invalid password is rejected', async () => {
const response = await signIn('customer@example.com', 'wrong-password');
expect(response.status).toBe(401);
});
});
The CLI recognizes [TC-42], TC-42, id-42, and
testcase-42, case-insensitively. Brackets make the marker easiest for people to see
and least likely to collide with ordinary test text.
Run Jest and inspect the XML
Run Jest normally. The configured reporter writes reports/junit.xml even while the
default reporter continues to print results to the terminal.
npx jest --ci
# Confirm the report exists and inspect its first lines
test -f reports/junit.xml
sed -n '1,30p' reports/junit.xml
A mapped result contains the marker in the test case name:
<testsuite name="Authentication" tests="1" failures="0">
<testcase
classname="Authentication"
name="[TC-42] customer can sign in with valid credentials"
time="0.12"
/>
</testsuite>
Upload Jest results to TestCollab
Set your token in the environment, then upload the XML to an existing TestCollab test plan:
export TESTCOLLAB_TOKEN="your-token"
npx @testcollab/cli report \
--project 123 \
--test-plan-id 456 \
--format junit \
--result-file ./reports/junit.xml
Replace 123 with the project ID and 456 with the test plan ID. Each
marked case must exist in that plan and be assigned to the user whose token performs the upload.
Complete GitHub Actions workflow
This workflow creates a plan from a CI tag, runs Jest, and uploads the JUnit report even when one
or more tests fail. Add TESTCOLLAB_TOKEN as a repository secret and the other values
as repository variables.
name: Jest tests
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
env:
TESTCOLLAB_TOKEN: ${{ secrets.TESTCOLLAB_TOKEN }}
TC_PROJECT_ID: ${{ vars.TC_PROJECT_ID }}
TC_CI_TAG_ID: ${{ vars.TC_CI_TAG_ID }}
TC_ASSIGNEE_ID: ${{ vars.TC_ASSIGNEE_ID }}
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install dependencies
run: npm ci
- name: Create TestCollab test plan
run: |
npx @testcollab/cli createTestPlan \
--project "$TC_PROJECT_ID" \
--ci-tag-id "$TC_CI_TAG_ID" \
--assignee-id "$TC_ASSIGNEE_ID"
- name: Run Jest
run: npx jest --ci
- name: Upload Jest results to TestCollab
if: always()
run: |
source tmp/tc_test_plan
npx @testcollab/cli report \
--project "$TC_PROJECT_ID" \
--test-plan-id "$TESTCOLLAB_TEST_PLAN_ID" \
--format junit \
--result-file ./reports/junit.xml
if: always() lets the result upload run after a failed Jest step. It does not turn a
failed test run into a successful workflow.
Use the EU region
For an EU account, add the following option to both createTestPlan and
report:
--api-url https://api-eu.testcollab.io
Avoid ambiguous numeric test names
Always prefer an explicit [TC-42] marker. A marker-free slug that ends in digits,
such as checkout-flow-42, is treated as TestCollab case 42. Ordinary prose such as
renders UTF-8 correctly is not. Adding the bracketed marker removes the ambiguity and
keeps the intended ID visible in Jest and JUnit output.
Troubleshooting
Jest finishes but reports/junit.xml is missing
Confirm jest-junit is installed and listed in reporters. If the workflow
overrides reporters on the command line, include both --reporters=default and
--reporters=jest-junit.
The XML does not contain [TC-42]
Set titleTemplate to {title}, rerun Jest, and inspect the
name attribute on the generated <testcase>. Put the marker in the
individual test title, not only in a surrounding describe block.
TestCollab cannot match a test case
Check that the numeric marker is the test case ID, the case is part of the target plan, and the plan execution is assigned to the token owner. A project ID or plan ID in the title will not map to a case.
The GitHub upload step is skipped
Keep Jest and tc report in separate steps and put if: always() on the
upload step. Also ensure Jest had enough time to write the XML before it exited.


