A reusable, composite GitHub Action that runs Go (Golang) unit tests following best practices: race-condition detection, code-coverage enforcement, HTML coverage reports, and artifact upload — all in a single action step.
| Feature | Details |
|---|---|
| Go toolchain setup | Uses actions/setup-go@v5 with automatic module-cache warming |
| Dependency verification | Runs go mod download && go mod verify to detect tampering |
| Static analysis | Runs go vet ./... before executing tests |
| Race detector | go test -race enabled by default |
| Coverage instrumentation | Atomic coverage mode; generates .out, .html, and .txt reports |
| Coverage threshold | Fails the step when total coverage falls below a configurable minimum |
| Workflow summary | Appends a Markdown coverage table to the GitHub job summary page |
| Artifact upload | Uploads all reports via actions/upload-artifact@v4 for later download |
| Cross-platform | Works on ubuntu-latest, macos-latest, and windows-latest |
Add the following step to any existing GitHub Actions workflow:
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Run Go unit tests
uses: jasonmiller-cc/golang-unit-testing@v1
with:
go-version: 'stable'
coverage-threshold: '80'| Input | Description | Required | Default |
|---|---|---|---|
go-version |
Go toolchain version (e.g. stable, 1.22, ^1.21). Passed to actions/setup-go. |
No | stable |
working-directory |
Path (relative to repo root) containing the Go module to test. | No | . |
test-flags |
Extra flags forwarded verbatim to go test (e.g. -count=1 -timeout 120s). |
No | (empty) |
coverage-threshold |
Minimum acceptable total coverage percentage (0–100). The step fails if coverage is below this value. | No | 0 |
upload-artifacts |
Set to "true" to upload coverage reports and test output; "false" to skip. |
No | true |
artifact-name |
Name of the uploaded artifact bundle. Must be unique per run if the action is invoked multiple times. | No | go-test-artifacts |
artifact-retention-days |
Days GitHub retains the artifact (1–90). | No | 30 |
run-vet |
Set to "true" to run go vet ./... before tests. |
No | true |
| Output | Description |
|---|---|
coverage-percentage |
Total code-coverage percentage as reported by go tool cover. |
test-result |
"pass" when all tests succeed; "fail" otherwise. |
Outputs can be consumed by downstream steps:
- name: Run Go unit tests
id: go-test
uses: jasonmiller-cc/golang-unit-testing@v1
- name: Print coverage
run: echo "Coverage is ${{ steps.go-test.outputs.coverage-percentage }}%"name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: jasonmiller-cc/golang-unit-testing@v1Fail the workflow when total coverage drops below 80 %:
- uses: jasonmiller-cc/golang-unit-testing@v1
with:
coverage-threshold: '80'name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: ['1.21', '1.22', 'stable']
steps:
- uses: actions/checkout@v4
- uses: jasonmiller-cc/golang-unit-testing@v1
with:
go-version: ${{ matrix.go-version }}
artifact-name: test-results-go${{ matrix.go-version }}Run each test only once and increase the default timeout:
- uses: jasonmiller-cc/golang-unit-testing@v1
with:
test-flags: '-count=1 -timeout 120s'Call the action once per module, giving each a unique artifact name:
steps:
- uses: actions/checkout@v4
- name: Test service-a
uses: jasonmiller-cc/golang-unit-testing@v1
with:
working-directory: './services/service-a'
artifact-name: 'coverage-service-a'
- name: Test service-b
uses: jasonmiller-cc/golang-unit-testing@v1
with:
working-directory: './services/service-b'
artifact-name: 'coverage-service-b'When upload-artifacts is "true" (the default), the following files are
bundled into a single artifact and uploaded to the workflow run:
| File | Description |
|---|---|
coverage.out |
Raw Go coverage profile (usable by go tool cover) |
coverage.html |
Browser-viewable HTML report with per-line highlighting |
coverage-summary.txt |
Function-level coverage percentages in plain text |
test-output.txt |
Verbose go test output captured via tee |
Download artifacts from the Actions tab → Artifacts section of any completed workflow run.
After every run the action appends a Markdown coverage table to the GitHub workflow summary page (visible in the Actions UI). Example:
Status: ✅ All tests passed
Total Coverage: 94.7%
Location Coverage github.com/you/repo/pkg/math.go: Add 100.0% github.com/you/repo/pkg/math.go: Divide 100.0%
This action uses Semantic Versioning (MAJOR.MINOR.PATCH).
Tags are created automatically by the
Release workflow whenever a commit is pushed
to main. The version bump is determined by the commit message type:
| Commit type | Bump |
|---|---|
feat: |
minor — x.Y.0 |
fix:, chore:, docs:, ci:, refactor:, test:, style: |
patch — x.y.Z |
BREAKING CHANGE: footer |
major — X.0.0 |
To pin to a specific major version in your workflow (recommended):
uses: jasonmiller-cc/golang-unit-testing@v1 # latest v1.x.x patch
uses: jasonmiller-cc/golang-unit-testing@v1.2 # latest v1.2.x patch
uses: jasonmiller-cc/golang-unit-testing@v1.2.3 # exact versionPull requests are welcome! Please read the Copilot instructions — the same rules apply to all human contributors.
All commit messages must follow Conventional Commits:
<type>(<scope>): <short description>
[optional body]
[optional footer]
CHANGELOG.md must be updated in every pull request.
Add your changes under ## [Unreleased] using the
Keep a Changelog format.
The release workflow will promote the unreleased section to a version heading
automatically when the PR is merged to main and a tag is created.