Skip to content

Testing Strategy

Sergio Hernandez edited this page Jul 24, 2026 · 1 revision

Testing Strategy

TrackHub tests in three layers, each with a different cost and a different guarantee. Knowing which layer catches which class of bug is the point — several production defects have shipped past a fully green suite because they belonged to a layer that was not running.

Related pages: Inter-Service Communication · Coding Standards


The three layers

Layer Location Guarantees Needs
Unit tests Each repository's tests/ Handler and reader logic (EF InMemory + Moq) Nothing
Contract tests TrackHub.IntegrationTests/ (its own solution) Every inter-service GraphQL call validates against the producer's real schema, and critical flows round-trip through real resolvers Sibling repository checkouts only — no database, no running services
Smoke tests tests/TrackHub.SmokeTests (local only, not published) The deployed stack works: health, tokens, authorization, real database A running local IIS deployment
# Contract tests — run after ANY change to a GraphQL surface or inter-service client
dotnet test TrackHub.IntegrationTests/TrackHub.IntegrationTests.slnx

They run in seconds, and a failure names the exact broken call.


Contract tests in detail

The suite covers these consumer → producer pairs:

Router → Manager · Router → Telemetry · Router → Geofence · Router → TripManagement · Reporting → Manager · Reporting → Telemetry · Reporting → Router · Reporting → Geofence · Reporting → TripManagement · Manager → Security · Manager → Router · Security → Manager · Geofencing → Manager · TripManagement → Manager · TripManagement → Telemetry

Layer A — contract validation

Every GraphQL query string a consumer ships — the exact internal const production sends, exposed via [assembly: InternalsVisibleTo] — is validated against the producer's real, in-process-built schema. A renamed or removed field, argument or input property fails the matching test with a message naming the call.

Layer B — round-trip execution

For critical and complex flows, the consumer's real reader or writer executes against the producer's real resolvers over an in-process IGraphQLClient; only the mediator (ISender) behind the resolvers is faked.

This catches what Layer A cannot: serialization drift — enum, UUID and DateTime coercion, casing, and field-to-property mapping on both sides.

Enum values travel in GraphQL variables and are invisible to Layer A. Every enum-typed literal a client sends (checkType, triggerType, result, …) must have a Layer B case asserting it coerces into the producer's enum. This is the single most common gap.

Layout

Project Purpose
src/TrackHub.ServiceContracts.Harness Test-support library: InProcessGraphQLClient (an IGraphQLClient over a producer IRequestExecutor), the client factory, and the producer schema/executor builder that reuses the production AddTrackHubGraphQLServer configuration
tests/TrackHub.ServiceContracts.Tests Contract and round-trip tests for every producer/consumer pair

The projects reference service source by relative path, so all TrackHub repositories must be cloned side by side.

The suite also exports each producer's SDL to TrackHub/schemas/<service>.graphql (the SchemaSdlExport test), which is what the portal's npm run codegen validates every frontend operation against. See Frontend.


What EF InMemory will not catch

Unit tests run against EF InMemory, which is fast and convenient — and diverges from PostgreSQL in ways that have shipped bugs:

Divergence Consequence
InMemory defaults to TrackAll; Manager's ApplicationDbContext is NoTracking A row fetched for mutation without Attach is silently discarded at SaveChangesAsync. The unit test passes.
InMemory has no json column semantics Distinct() / GroupBy() over an entity containing transporter_position.attributes fails on real PostgreSQL with 42883. The unit test passes.
InMemory evaluates un-translatable LINQ client-side OrderBy over a projected record struct member, or GroupBy(...).Select(g => g.First()), throws only against Npgsql. The unit test passes.

The cheap guard

Infrastructure.UnitTests/WorkforceQueryTranslationTests demonstrates the pattern: run the reader against the real Npgsql provider pointed at an unreachable host. Translation happens before the connection is attempted, so a translation failure is distinguishable from a connection failure — and no database is required.

Use this whenever a query's translatability is in question.


What handler unit tests do not cover

Handler unit tests do not run the pipeline. They call the handler directly, so none of the behaviors execute:

  • [Authorize] is not enforced
  • AccountScopeBehavior does not run — tenant isolation is untested
  • [Caching] and [RateLimiting] are inert
  • validators do not run

The behavior tests in Common.Application.Tests/Behaviors/AccountScopeBehaviorTests are the only proof that tenant scoping works. The per-service AccountScopeCoverageTests gate proves every request type is classified, but not that the classification is correct.


Rules

  • Never re-type a GraphQL query string in a test. Reference the production internal const, or the test validates a copy that has already drifted.
  • Mutation-test every guard. A green suite is not evidence a guard works — twice, an audit has found a HIGH-severity bug behind a fully green suite because a test asserted the buggy behaviour. Break the guard deliberately and confirm the test goes red.
  • Every new inter-service call gets a Layer A test, and complex or critical ones get a Layer B round-trip.
  • Provider mappers carry a value-correctness unit test. The provider fetch and mapping is the money path — speed, odometer and position feed reports, and billing.
  • Assert round-trip properties, not literals, for anything timezone-dependent. A datetime-local helper that skips the offset shift round-trips correctly under TZ=UTC, which is exactly what dev boxes and CI run.

The gates

Scope Command
A backend repository dotnet build && dotnet test for its solution
Any GraphQL surface change dotnet test TrackHub.IntegrationTests/TrackHub.IntegrationTests.slnx
The portal npm run typecheck && npm test && npm run build
Help content npm run help:check (also runs on predev / prebuild)
A deployed environment tests/TrackHub.SmokeTests against the running stack

Clone this wiki locally