Help Center/Automation & CI/CD/Your test framework

Jest: Generate JUnit XML with jest-junit and Upload to TestCollab

Configure jest-junit to generate JUnit XML, map Jest tests to TestCollab cases, and upload results from local runs or GitHub Actions.

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.

Last updated 2026-08-26.