Grafana — Testing#
Test metrics#
- Test files: 1,692
- Total Go files: 5,867
- Ratio (test files / source files): ~29% — approximately 1 test file for every 3.5 source files
- Test frameworks: testify (
require+assert) in 1,739 files — effectively universal; stdlibtestingfor everything else;gomocknot used; mockery v2.53.4 for code-generated mocks in specific packages
Test organization#
- Placement: Predominantly same-package (white-box). 125 files use the
_testpackage suffix (black-box), concentrated in packages with a stable public API surface (e.g.pkg/apimachinery/, somepkg/services/packages). - Helper packages:
pkg/tests/testinfra—StartGrafana()/StartGrafanaEnv()utilities that spin up a complete Grafana server (SQLite-backed, real Wire DI, listening on a random TCP port) for HTTP-level integration tests. This is the backbone ofpkg/tests/api/**andpkg/tests/apis/**.pkg/tests/testsuite—testsuite.Run(m)TestMain wrapper invoked in every integration test package. Provides consistent test-suite lifecycle (shared setup, global cleanup).pkg/tests/apis—apis.NewK8sTestHelper(t, GrafanaOpts{...})convenience wrapper that boots a live server, configures feature toggles, and returns a Kubernetes dynamic client + REST config for testing the new k8s-style API server.pkg/services/ngalert/tests/fakes/— large hand-written fake collection for alerting storage contracts (RuleStore, KVStore, permissions, provisioning). Each fake carries at *testing.T, an in-memory data store, aHook func(cmd any) errorfor injecting errors, and aRecordedOps []anyslice for call history assertions.pkg/services/*/fakes/— pattern repeated across dozens of domain services (datasources, secrets, cloud migrations, auth API, library elements, etc.).pkg/services/accesscontrol/mock/— manual mock for access control, separate from fakes; used where you need controllable return values without full store semantics.pkg/storage/unified/sql/db/mocks/— mockery-generated mocks for the database abstraction interfaces (DB,Tx,Row,Rows), used in storage unit tests.pkg/util/testutil/— general utilities includingSkipIntegrationTestInShortMode(t)guard andmocks/sub-package.apps/dashboard/pkg/migration/testutil/— bespokeChecksumStorefor golden-checksum verification of schema migration outputs.- Numerous
testdata/directories containing JSON fixtures, golden snapshots, SQL migration files, and binary data.
- Fixtures: testdata directories across ~90 packages; JSON response snapshots in
pkg/tests/api/alerting/test-data/; golden checksum JSON files inapps/dashboard/pkg/migration/testdata/. Schema migration test inputs are collections of versioned JSON dashboard documents.
Test patterns#
Table-driven tests#
- Prevalence: Heavy — 13,629 occurrences of table-driven patterns (
tests := [],testCases,t.Run,tc.nameetc.) in*_test.gofiles - Style: Anonymous struct slices are dominant. Pattern:
tests := []struct { name string input SomeType expected SomeType wantErr bool }{ {name: "...", input: ..., expected: ..., wantErr: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ... }) } - Example:
pkg/tsdb/loki/parse_query_test.go— dozens of named cases covering Loki query expression transformations;pkg/services/ngalert/eval/— evaluation path tests over alert rule variants.
Mocking approach#
Strategy: Two-tier:
- Hand-written fakes — used in high-churn domain packages (alerting, provisioning, datasources, secrets). Fakes implement the service interface and maintain in-memory state. They often accept a
t *testing.Tand exposeRecordedOpsso callers can assert what calls were made. Injection point hooks (Hook func(cmd any) error) allow targeted error injection without subclassing.// pkg/services/ngalert/tests/fakes/rules.go type RuleStore struct { t *testing.T mtx sync.Mutex Rules map[int64][]*models.AlertRule Hook func(cmd any) error RecordedOps []any } - Generated mocks via mockery v2 — used for low-level interface contracts (database abstractions, Git operations, provisioning repository interfaces).
//go:generate mockery --name Foo --with-expecterannotations on the interface file; generated files carry aDO NOT EDITheader and usetestify/mockwith typed expectation chaining (.EXPECT().Method().Return(...)).
- Hand-written fakes — used in high-churn domain packages (alerting, provisioning, datasources, secrets). Fakes implement the service interface and maintain in-memory state. They often accept a
Dominant choice: Hand-written fakes in the business logic layer; mockery in infrastructure/storage boundaries. This reflects the team’s philosophy: fakes evolve alongside domain logic, while infrastructure interfaces are stable enough for generation.
Integration tests#
- Present: Yes — 19+
_integration_test.gofiles and hundreds of tests inpkg/tests/api/andpkg/tests/apis/that start a full Grafana server. - How: Two mechanisms:
- Full server tests (
pkg/tests/apis/) —testinfra.StartGrafanaEnv(t, ...)spins up a complete Grafana process (full Wire-initialized DI graph, SQLite database, real HTTP listeners on random ports). Tests then use a Kubernetes dynamic client or plain HTTP to exercise the system end-to-end. Feature toggles are passed asGrafanaOpts.EnableFeatureTogglesto gate the k8s API migration paths.helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ AppModeProduction: true, EnableFeatureToggles: []string{"..."}, }) client := helper.NewDiscoveryClient() - Named integration tests (
_integration_test.go) — cover storage-level concerns: Redis/Memcached cache, PostgreSQL/MySQL-specific SQL, email sending, LDAP, CloudWatch calls. These require external services and are run withmake test-go-integration(or with specific devenv sources).
- Full server tests (
- Separation: Two-pronged:
- Naming convention:
_integration_test.gofiles for external-service-dependent tests. - Guard call at the top of every integration test function:
testutil.SkipIntegrationTestInShortMode(t)— 904 usages. This skips whentesting.Short()is true, allowinggo test -shortto run only fast unit tests. - Some tests are additionally gated on
db.IsTestDbSQLite()ort.Skip("test only on sqlite for now")for DB-engine-specific behavior.
- Naming convention:
- Async assertions:
require.Eventuallyused 232 times — primarily in alerting scheduler tests where goroutines must converge to a state, and in integration tests waiting for background services to start.
Snapshot / golden file tests#
- API snapshots:
pkg/tests/api/alerting/test-data/alert-notifiers-v1-snapshot.jsonand similar — JSON responses captured at a known-good state. Tests re-read the snapshot and compare; a flag path in the test can rewrite it when intentional changes occur. - Golden checksum store:
apps/dashboard/pkg/migration/testutil.ChecksumStore— SHA-256 checksums of migration output files stored in a JSON manifest.TestMainloads/saves checksums; setREGENERATE_CHECKSUMS=trueto update. This is a space-efficient alternative to storing full golden files for large migrations. - OpenAPI snapshots:
pkg/tests/apis/openapi_snapshots/— captures the OpenAPI spec output for each API group. Regression-tested to detect inadvertent API schema changes.
Test quality observations#
What’s done well#
- testinfra is exemplary. Spinning up the real Grafana process in tests — full DI graph, real database — is the gold standard for catching integration issues. It means API tests cover the full stack: Wire wiring, middleware, authentication, database, and business logic simultaneously.
- Separation of fast and slow tests via
SkipIntegrationTestInShortModeis consistent and enables CI to run unit tests cheaply while heavy integration tests run in a dedicated job. - Hand-written fakes with
Hookinjection are more flexible than mockery for domain-heavy code. TheRecordedOpspattern (recording every call for later assertion) is a clean substitute forgomock’s expectation DSL in stateful scenarios. - Golden checksum pattern in schema migration tests is a compact way to regression-test a large combinatorial space (many dashboard JSON versions × many migration paths) without storing MBs of golden files.
require.Eventuallyis used appropriately for timing-sensitive tests involving goroutines and lifecycle state machines, rather than sleeping.t.Cleanup()is used pervasively for teardown, ensuring cleanup runs even on failure and eliminating the need fordeferchains.
What could improve#
- Two divergent mocking philosophies (hand-written fakes vs. mockery) create maintenance overhead. New contributors must learn which pattern applies where. A project-wide decision recorded in AGENTS.md would help.
- No
//go:build integrationbuild tag — integration test isolation relies solely on theSkipIntegrationTestInShortModecall inside the test function. A build tag would allowgo test ./...to exclude integration tests at the package level without loading their (sometimes heavy) imports. - Some
_test.gopackages forpkg/tests/api/have expensive compilation due to the full Grafana dependency graph. The AGENTS.md warns thatpkg/api/can take ~2 minutes to compile, discouraging iterative local testing. - Missing test coverage metrics — no visible coverage threshold enforcement in CI (no
go test -coverprofileor codecov gate). For a project of this size, enforcing coverage minimums per package would catch gaps.
Patterns worth emulating#
testinfra.StartGrafana()pattern — the idea of aStartTestServer(t, opts)function that boots the full production DI graph with an in-memory DB is highly portable to any Wire-based service. It provides integration confidence that a mock-heavy approach can never match.Hook func(cmd any) errorin hand-written fakes — a simple escape hatch that avoids subclassing or mock-framework complexity. Any test can inject arbitrary errors at precise call points without modifying the fake.ChecksumStorefor migration golden tests — the pattern of storing SHA-256 checksums of deterministic outputs rather than the outputs themselves scales well to large generated-file workloads and is worth generalizing.require.Eventuallyovertime.Sleep— using testify’sEventually(condition, timeout, interval)instead of sleeping keeps async tests fast when the condition is met early and provides a meaningful failure message when it isn’t.