Claude
Skills
Sign in
Back

dotnet-gha-build-test

Included with Lifetime
$97 forever

Configuring .NET build/test in GitHub Actions. setup-dotnet, NuGet caching, test reporting.

Cloud & DevOps

What this skill does


# dotnet-gha-build-test

.NET build and test workflow patterns for GitHub Actions: `actions/setup-dotnet@v4` configuration with multi-version installs and NuGet authentication, NuGet restore caching for fast CI, `dotnet test` with result publishing via `dorny/test-reporter`, code coverage upload to Codecov and Coveralls, multi-TFM matrix testing across net8.0 and net9.0, and test sharding strategies for large projects.

**Version assumptions:** `actions/setup-dotnet@v4` for .NET 8/9/10 support. `dorny/test-reporter@v1` for test result visualization. Codecov and Coveralls GitHub Apps for coverage reporting.

**Scope boundary:** This skill owns .NET build and test pipeline configuration for GitHub Actions. Starter CI templates (basic build/test/pack) are owned by [skill:dotnet-add-ci]. Composable workflow patterns (reusable workflows, matrix strategies, caching) are in [skill:dotnet-gha-patterns]. Testing strategy guidance (what to test, test architecture, quality gates) is owned by [skill:dotnet-testing-strategy]. Benchmark CI workflows are owned by [skill:dotnet-ci-benchmarking].

**Out of scope:** Starter CI templates -- see [skill:dotnet-add-ci]. Test architecture and strategy -- see [skill:dotnet-testing-strategy]. Benchmark regression detection in CI -- see [skill:dotnet-ci-benchmarking]. Publishing and deployment -- see [skill:dotnet-gha-publish] and [skill:dotnet-gha-deploy]. Azure DevOps build/test pipelines -- see [skill:dotnet-ado-build-test].

Cross-references: [skill:dotnet-add-ci] for starter build/test templates, [skill:dotnet-testing-strategy] for test architecture guidance, [skill:dotnet-ci-benchmarking] for benchmark CI integration, [skill:dotnet-artifacts-output] for artifact upload path adjustments when using centralized build output layout.

---

## `actions/setup-dotnet@v4` Configuration

### Basic Setup

```yaml
steps:
  - uses: actions/checkout@v4

  - name: Setup .NET
    uses: actions/setup-dotnet@v4
    with:
      dotnet-version: '8.0.x'
```

### Multi-Version Install

Install multiple SDK versions for multi-TFM builds within a single job:

```yaml
- name: Setup .NET SDKs
  uses: actions/setup-dotnet@v4
  with:
    dotnet-version: |
      8.0.x
      9.0.x
```

The first listed version becomes the default `dotnet` on PATH. All installed versions are available via `--framework` targeting.

### NuGet Authentication for Private Feeds

Configure NuGet source authentication via `actions/setup-dotnet@v4`:

```yaml
- name: Setup .NET with NuGet auth
  uses: actions/setup-dotnet@v4
  with:
    dotnet-version: '8.0.x'
    source-url: https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json
  env:
    NUGET_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```

For multiple private feeds, configure additional sources after setup:

```yaml
- name: Setup .NET
  uses: actions/setup-dotnet@v4
  with:
    dotnet-version: '8.0.x'

- name: Add private NuGet feed
  run: |
    set -euo pipefail
    dotnet nuget add source https://pkgs.dev.azure.com/myorg/_packaging/myfeed/nuget/v3/index.json \
      --name AzureArtifacts \
      --username az \
      --password ${{ secrets.AZURE_ARTIFACTS_PAT }} \
      --store-password-in-clear-text
```

The `--store-password-in-clear-text` flag is required on Linux runners where DPAPI encryption is unavailable.

### Global.json SDK Version Pinning

When `global.json` exists in the repository root, `actions/setup-dotnet@v4` can read it automatically:

```yaml
- name: Setup .NET from global.json
  uses: actions/setup-dotnet@v4
  with:
    global-json-file: global.json
```

This ensures CI uses the same SDK version as local development.

---

## NuGet Restore Caching

### Standard Cache Configuration

```yaml
- name: Cache NuGet packages
  uses: actions/cache@v4
  with:
    path: ~/.nuget/packages
    key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/Directory.Packages.props') }}
    restore-keys: |
      nuget-${{ runner.os }}-

- name: Restore dependencies
  run: dotnet restore MySolution.sln
```

### Built-in Cache with setup-dotnet

`actions/setup-dotnet@v4` has built-in caching support using `packages.lock.json`:

```yaml
- name: Setup .NET with caching
  uses: actions/setup-dotnet@v4
  with:
    dotnet-version: '8.0.x'
    cache: true
    cache-dependency-path: '**/packages.lock.json'
```

Generate lock files locally first: `dotnet restore --use-lock-file`. Commit `packages.lock.json` files for deterministic restore.

### Cache Key Strategy

| Key Component | Purpose |
|---------------|---------|
| `runner.os` | Prevent cross-OS cache collisions |
| `hashFiles('**/*.csproj')` | Invalidate when package references change |
| `hashFiles('**/Directory.Packages.props')` | Invalidate when centrally managed versions change |
| `restore-keys` prefix | Partial match for incremental cache reuse |

---

## Test Result Publishing

### dorny/test-reporter

Publish `dotnet test` results as GitHub Actions check annotations with inline failure details:

```yaml
- name: Test
  run: |
    set -euo pipefail
    dotnet test MySolution.sln \
      --configuration Release \
      --logger "trx;LogFileName=test-results.trx" \
      --results-directory ./test-results
  continue-on-error: true
  id: test

- name: Publish test results
  uses: dorny/test-reporter@v1
  if: always()
  with:
    name: '.NET Test Results'
    path: 'test-results/**/*.trx'
    reporter: dotnet-trx
    fail-on-error: true
```

**Key decisions:**

- `continue-on-error: true` on the test step ensures the reporter step always runs, even on failures
- `if: always()` on the reporter step publishes results regardless of test outcome
- `fail-on-error: true` on the reporter marks the check as failed when tests fail

### Alternative: EnricoMi/publish-unit-test-result-action

For richer PR comment integration with test counts:

```yaml
- name: Publish test results
  uses: EnricoMi/publish-unit-test-result-action@v2
  if: always()
  with:
    files: 'test-results/**/*.trx'
    check_name: 'Test Results'
```

---

## Code Coverage Upload

### Codecov

```yaml
- name: Test with coverage
  run: |
    set -euo pipefail
    dotnet test MySolution.sln \
      --configuration Release \
      --collect:"XPlat Code Coverage" \
      --results-directory ./coverage

- name: Upload coverage to Codecov
  uses: codecov/codecov-action@v4
  with:
    directory: ./coverage
    fail_ci_if_error: false
    token: ${{ secrets.CODECOV_TOKEN }}
```

### Coveralls

```yaml
- name: Test with coverage
  run: |
    set -euo pipefail
    dotnet test MySolution.sln \
      --configuration Release \
      --collect:"XPlat Code Coverage" \
      --results-directory ./coverage

- name: Upload coverage to Coveralls
  uses: coverallsapp/github-action@v2
  with:
    file: coverage/**/coverage.cobertura.xml
    format: cobertura
    github-token: ${{ secrets.GITHUB_TOKEN }}
```

### Coverage Report Generation with ReportGenerator

Generate human-readable HTML coverage reports alongside CI upload:

```yaml
- name: Generate coverage report
  run: |
    set -euo pipefail
    dotnet tool install -g dotnet-reportgenerator-globaltool
    reportgenerator \
      -reports:coverage/**/coverage.cobertura.xml \
      -targetdir:coverage-report \
      -reporttypes:HtmlInline_AzurePipelines\;Cobertura

- name: Upload coverage report
  uses: actions/upload-artifact@v4
  with:
    name: coverage-report
    path: coverage-report/
    retention-days: 30
```

---

## Multi-TFM Matrix Testing

### Matrix Strategy for TFMs

```yaml
jobs:
  test:
    strategy:
      fail-fast: false
      matrix:
        tfm: [net8.0, net9.0]
        os: [ubuntu-latest, windows-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: |
            8.0.x
            9.0.x

      - name: Cache NuGet
        uses: actions/cache@v4
        with:
          path: ~/.nuget/packages
          key: 

Related in Cloud & DevOps