Skip to main content

CI/CD Reporting

TUnit provides built-in integration with continuous integration and deployment platforms, automatically detecting and adapting to various CI environments.

GitHub Actions Reporter​

When running in GitHub Actions, TUnit automatically generates a test summary that appears in the workflow run summary. This feature activates when the GITHUB_ACTIONS environment variable is detected.

Automatic Detection​

The GitHub reporter automatically enables when:

  • The GITHUB_ACTIONS environment variable is set
  • The GITHUB_STEP_SUMMARY environment variable points to a valid file

No additional configuration is required for basic functionality.

info

Forgejo, a free open source alternative to GitHub, also sets these environment variables for compatibility. Starting with Forgejo version 17.0 and Forgejo Runner version 13.2, the test summary works without additional configuration. See Forgejo 17 announcement for details.

Reporter Styles​

TUnit's GitHub reporter supports two output styles:

Collapsible Style (Default)​

The collapsible style provides a clean, concise summary with expandable details:

### TestAssembly (.NET 9.0)

| Test Count | Status |
| --- | --- |
| 95 | Passed |
| 5 | Failed |

<details>
<summary>Test Details (click to expand)</summary>

### Details
| Test | Status | Details | Duration |
| --- | --- | --- | --- |
| MyTest.ShouldFail | Failed | Expected: true, Actual: false | 123ms |
| ... | ... | ... | ... |

</details>

This is the default style as of TUnit v1.0.0, designed to:

  • Keep workflow summaries clean and navigable
  • Allow quick overview of test results
  • Provide full details on demand
  • Work well with large test suites

Full Style (Legacy)​

The full style displays all test details directly in the summary:

### TestAssembly (.NET 9.0)

| Test Count | Status |
| --- | --- |
| 95 | Passed |
| 5 | Failed |

### Details
| Test | Status | Details | Duration |
| --- | --- | --- | --- |
| MyTest.ShouldFail | Failed | Expected: true, Actual: false | 123ms |
| ... | ... | ... | ... |

Configuration Options​

You can control the GitHub reporter style using either command-line arguments or environment variables:

Command Line​

# Use collapsible style (default)
dotnet test -- --github-reporter-style collapsible

# Use full style (legacy behavior)
dotnet test -- --github-reporter-style full

Environment Variable​

# Set reporter style via environment variable
export TUNIT_GITHUB_REPORTER_STYLE=collapsible # or 'full'
dotnet test
# In GitHub Actions workflow
- name: Run Tests
env:
TUNIT_GITHUB_REPORTER_STYLE: collapsible
run: dotnet test

GitHub Actions Workflow Example​

name: Test

on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: '9.0.x'

- name: Run Tests with Collapsible Reporter
run: dotnet test -- --output Detailed
# GitHub reporter auto-detects and uses collapsible style by default

- name: Run Tests with Full Reporter
run: dotnet test -- --github-reporter-style full
# Explicitly use full style for all details

Disabling the GitHub Reporter​

If you need to disable the GitHub reporter entirely:

Via Environment Variable​

export TUNIT_DISABLE_GITHUB_REPORTER=true
# or (deprecated legacy name, kept for backwards compatibility)
export DISABLE_GITHUB_REPORTER=true
# In GitHub Actions workflow
- name: Run Tests Without GitHub Reporter
env:
TUNIT_DISABLE_GITHUB_REPORTER: true
run: dotnet test

Multiple Test Projects​

Running several test projects in one pipeline produces one summary section per project. To merge them into a single aggregated block (and a single combined HTML report), see Aggregated Reports.

File Size Limitations​

The GitHub reporter respects GitHub's file size limits:

  • Maximum summary file size: 1MB
  • If the output would exceed this limit, the reporter will skip writing to prevent errors
  • Consider using the collapsible style for large test suites to reduce summary size

Filtering Test Output​

When using the GitHub reporter, only non-passing tests are included in the details section:

  • Failed tests
  • Skipped tests
  • Timed out tests
  • Cancelled tests
  • Tests that never completed (in progress)

Passed tests are counted but not listed in details to keep the summary focused on actionable items.

Other CI Platforms​

While TUnit currently provides specialized support for GitHub Actions, it works with all CI/CD platforms through standard test output formats:

Azure DevOps​

Use TRX reporting for Azure DevOps integration:

dotnet test -- --report-trx --report-trx-filename TestResults.trx

Jenkins​

Jenkins can consume various test output formats:

# Generate TRX report for Jenkins
dotnet test -- --report-trx

# Or use console output with appropriate verbosity
dotnet test -- --output Detailed

GitLab CI​

GitLab's test report feature expects JUnit XML format. TRX files are not JUnit XML, so you need a conversion step:

test:
script:
- dotnet test -- --report-trx
- dotnet tool install -g trx2junit
- trx2junit TestResults/*.trx
artifacts:
reports:
junit:
- TestResults/*.xml

The trx2junit tool converts TRX output to JUnit XML that GitLab can parse.

Unobserved Task Exceptions​

A task that faults when nothing awaits it, such as a fire-and-forget Task.Run or the losing branch of a Task.WhenAny, raises TaskScheduler.UnobservedTaskException when the runtime garbage-collects it. TUnit keeps the process alive and records each of these exceptions for the session:

  • The console prints a warning with the count and each exception at the end of the run.
  • The HTML report shows them in an Unobserved task exceptions panel in the Overview view.
  • The JUnit report puts them in the test suite's <system-err>.
  • TestSessionContext.UnobservedTaskExceptions lists the exceptions recorded so far, for example in an [After(TestSession)] hook.

The runtime raises the event on the finalizer thread, often after the test that started the task has finished, so TUnit cannot tie the exception to one test. The exception's stack trace usually names the code that started the task.

By default these exceptions do not fail the run. To gate CI on them, pass --fail-on-unobserved-task-exceptions or set context.Settings.Execution.FailOnUnobservedTaskExceptions = true. With the option, TUnit runs a final garbage collection at the end of the session so late faults are not lost, and reports each exception as a failed result named Unobserved task exception - <type>: <message>. That result appears in TRX and every other report, and the run exits with a failure code.

When an IDE runs several test sessions in the same process at once, TUnit cannot tell which session an exception belongs to. Every running session then reports it, but none of them fails because of it.

public static class UnobservedTaskExceptionLog
{
[After(TestSession)]
public static void LogUnobservedTaskExceptions(TestSessionContext context)
{
foreach (var exception in context.UnobservedTaskExceptions)
{
Console.WriteLine($"Unobserved task exception: {exception.Message}");
}
}
}

Environment Detection​

TUnit reads a small set of CI environment variables to enable platform-specific behavior:

VariableUsed for
GITHUB_ACTIONSEnables the GitHub Actions reporter and workflow summary integration
GITHUB_STEP_SUMMARYDestination file for the GitHub Actions job summary
GITLAB_CIDetects GitLab CI runs
CI_SERVERGeneric CI detection signal

Other CI systems (Azure DevOps, Jenkins, CircleCI, Travis CI, AppVeyor, TeamCity, etc.) do not have TUnit-specific detection logic. They integrate through the standard Microsoft.Testing.Platform test result outputs such as --report-trx and --report-junit, which every major CI platform can ingest.

Best Practices​

  1. Use Collapsible Style for Large Test Suites: The default collapsible style keeps summaries manageable
  2. Filter Tests in CI: Use --treenode-filter to run specific test subsets in different CI jobs
  3. Set Appropriate Timeouts: Use --timeout to prevent hanging builds
  4. Enable Fail-Fast for Quick Feedback: Use --fail-fast in PR validation builds
  5. Customize Verbosity: Adjust --output based on your debugging needs

Troubleshooting​

GitHub Reporter Not Working​

If the GitHub reporter isn't generating summaries:

  1. Check Environment Variables:

    echo $GITHUB_ACTIONS # Should output 'true'
    echo $GITHUB_STEP_SUMMARY # Should point to a file
  2. Verify Not Disabled:

    # Ensure these are not set
    echo $TUNIT_DISABLE_GITHUB_REPORTER
    echo $DISABLE_GITHUB_REPORTER # deprecated legacy name
  3. Check File Permissions: Ensure the process can write to GITHUB_STEP_SUMMARY file

  4. Review Test Output: Run with --diagnostic to see detailed logs

Summary Not Appearing​

If tests run but no summary appears:

  • Check if all tests passed (summaries may be minimal for all-passing runs)
  • Verify the workflow has completed (summaries appear after job completion)
  • Check the "Summary" section of the GitHub Actions run page

Large Summary Files​

If you encounter file size issues:

  • Switch to collapsible style: --github-reporter-style collapsible
  • Filter tests to reduce output: --treenode-filter "critical-tests"
  • Run tests in separate jobs with focused test subsets