Testing
Automated testing is a mandatory part of the Definition of Done. It ensures that changes work as intended and guards against regressions. This section defines the standards for writing tests on both backend and frontend.
12.1 General Principles
Tests are code: apply the same quality standards—readability, DRY, meaningful naming.
Test behavior, not implementation: tests should only break when the externally visible behavior changes, not when internal refactoring occurs.
Arrange‑Act‑Assert (AAA): structure every test into three clearly separated blocks. Use blank lines or comments to separate them.
One logical assertion per test: test a single scenario. Multiple asserts are acceptable if they verify one outcome from different angles (e.g., properties of a result object).
Deterministic: tests must not depend on timing, random values, external services, file system order, thread scheduling, or time zones. Use mocks, fakes, and fixed clock implementations.
Fast: unit tests must run in milliseconds. Integration tests should complete in a reasonable time and run in CI. Avoid slow tests in the developer’s normal workflow.
12.2 Naming Convention
Follow the pattern: MethodName_Scenario_ExpectedResult (for C# and TypeScript).
Use underscores to separate parts.
MethodName– the method under test.Scenario– the condition being tested (e.g.,ValidUser,NullInput,DatabaseDown).ExpectedResult– the expected outcome (e.g.,ReturnsUser,ThrowsException,ReturnsEmptyList).
Examples:
GetUser_ValidId_ReturnsUserCreateOrder_InvalidAmount_ThrowsValidationExceptionDeleteUser_UserNotFound_ThrowsNotFound
12.3 Test Structure (AAA Pattern)
12.4 Test Coverage
Coverage targets (enforced in CI, part of DoD):
Line coverage: minimum 80% for new code, unless the project has a separately agreed threshold.
Branch coverage: minimum 70%.
For legacy code, coverage must not decrease with new pull requests.
What to exclude from coverage?
Auto‑generated code (protobuf, GraphQL clients, etc.).
Simple DTOs, models with only auto‑properties.
Application startup / bootstrapping code (can be covered by integration / smoke tests).
Configuration binding POCOs.
When coverage cannot be reached, the team lead may grant an exception documented in a Jira ticket, with a plan to reach the target later.
12.5 Unit Tests
Unit tests isolate a single unit (class, component, service) and mock all its dependencies.
C# specific rules:
Use xUnit, NUnit, or MSTest (choose one per solution; xUnit is recommended for new projects).
Mock all dependencies using Moq (or NSubstitute). Do not use production implementations of dependencies.
Test all public methods; private methods are tested indirectly through public ones.
Test exceptions with
Assert.Throws<T>.Test asynchronous code with
async Tasktest methods.
Angular / TypeScript specific rules:
Use Jasmine + Karma or Jest (Jest is recommended for new projects). Choose one per repository.
For services, use spies and stubs. For components, use
TestBedwith shallow or deep rendering as needed.Always call
fixture.detectChanges()after setting inputs and before asserting.Test templates using
DebugElementqueries (query(By.css('...'))).Validate that pipe transforms, directives, and route resolvers are covered.
12.6 Integration / Component Tests
Scope: test interactions between real components (e.g., service with a real in‑memory database, HTTP client with a mock server). Full stack integration tests that require deployed environments are covered by end‑to‑end tests.
C#: use
WebApplicationFactoryto test the ASP.NET pipeline with a mocked backend or an in‑memory database. Verify middleware, validation, authentication, and serialization.Angular: integration tests may use
TestBedwith real services when the service itself does not have heavy dependencies. Use Angular’sHttpClientTestingModuleto mock HTTP requests.
12.7 End‑to‑End Tests (optional but recommended)
If e2e tests are introduced, they should be written using a modern framework (Playwright, Cypress, or Selenium with an agreed model) and cover only critical business flows.
E2e tests are not blockers in the DoD for standard tasks unless the story explicitly requires them, but they must pass in the release pipeline.
12.8 Test Data and Helpers
Use builders or factories (e.g.,
TestDataFactory.CreateUser()) to avoid duplication.Do not hardcode “magic” test values without context; name test data descriptively (
const validUserId = "123").Avoid shared mutable state between tests; each test must be independent and clean up after itself.
12.9 Running Tests in CI
Every PR must trigger
dotnet test(C#) andng testorjest(Angular) in CI. Tests must pass before merging.Coverage reports are generated and compared against the threshold. A failing threshold fails the build.
Test results are published to the CI dashboard; flaky tests must be fixed or marked as ignored with a ticket reference and justification.