Skip to content

How to: Run Tests

Overview

SyRF has automated tests for both .NET services (xUnit) and the Angular web service (Vitest). Tests run automatically in CI/CD on pull requests and before deployments.

Test Infrastructure

Service Type Framework Test Count Location
Angular (Web) Vitest 612+ src/services/web/
.NET (API, PM, etc.) xUnit 5 projects src/services/*/, src/libs/*/

Running Tests Locally

Using the Test Script

A unified test runner script is available:

# Run all tests
.github/scripts/run-tests.sh

# Run only Angular tests
.github/scripts/run-tests.sh web

# Run only .NET tests
.github/scripts/run-tests.sh dotnet

# Run with coverage
.github/scripts/run-tests.sh all --coverage

Running Angular Tests Directly

cd src/services/web

# Run tests once
pnpm exec ng test --no-watch

# Run with coverage
pnpm exec ng test --no-watch --coverage

# Run in watch mode (development)
pnpm exec ng test

Coverage output: src/services/web/coverage/

Running Specific Angular Tests

Use --include to run specific test files and --filter to match test names:

# Run a specific test file
pnpm exec ng test --no-watch --include="src/app/core/services/signal-r/signal-r.service.spec.ts"

# Run all tests in a folder (glob pattern)
pnpm exec ng test --no-watch --include="src/app/core/services/**/*.spec.ts"

# Filter by test name (regex matches describe/it blocks)
pnpm exec ng test --no-watch --filter="_connect"

# Combine file and name filtering
pnpm exec ng test --no-watch \
  --include="src/app/core/services/signal-r/signal-r.service.spec.ts" \
  --filter="_connect"

# Watch mode with specific file
pnpm exec ng test --include="src/app/core/services/signal-r/signal-r.service.spec.ts"

Note: Always use pnpm exec ng test (not pnpm exec vitest directly) to ensure Angular's build system properly compiles decorators and handles test teardown.

Running .NET Tests Directly

# Run all .NET tests
dotnet test syrf.sln

# Run with coverage
dotnet test syrf.sln \
  --collect:"XPlat Code Coverage" \
  -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura

# Run specific service tests
dotnet test src/services/api/SyRF.API.Endpoint.Tests/

Running Specific .NET Tests

# Run a specific test project
dotnet test src/services/api/SyRF.API.Endpoint.Tests/

# Filter by test name (supports wildcards)
dotnet test --filter "FullyQualifiedName~SearchImportJob"

# Filter by test class
dotnet test --filter "ClassName=SearchControllerTests"

# Filter by test method
dotnet test --filter "Name=ShouldReturnSearchResults"

# Combine project and filter
dotnet test src/services/project-management/SyRF.ProjectManagement.Endpoint.Tests/ \
  --filter "FullyQualifiedName~SearchImportJobStateMachine"

CI/CD Test Integration

Tests run automatically in two workflows:

1. Pull Request Tests (pr-tests.yml)

Runs on ALL pull requests to main:

PR opened/updated
    ↓
detect-changes
    ↓
test-dotnet (if .NET changed) ──┬── test-summary
test-web (if Angular changed) ──┘

Key features:

  • Runs on every PR (not just preview-labeled)
  • Only tests changed services (saves time)
  • Uploads test results and coverage as artifacts
  • Fails PR if tests fail

2. Main Branch Tests (ci-cd.yml)

Runs before deployment on push to main:

detect-changes → version → test-dotnet ──┬── build-docker → deploy
                         → test-web ─────┘

Key features:

  • Tests must pass before Docker builds
  • Blocks deployment if tests fail
  • Coverage reports uploaded as artifacts

Coverage Thresholds

Angular tests have coverage thresholds configured in vitest.config.ts:

Metric Threshold
Statements 50%
Branches 40%
Functions 50%
Lines 50%

Tests will fail if coverage drops below these thresholds.

Code Quality (SonarCloud)

Code quality and coverage analysis is performed by SonarCloud on every PR.

What You Get

  • Quality Gate: Pass/fail status on PRs based on quality thresholds
  • Coverage Analysis: Line and branch coverage visualization
  • Code Smells: Detect maintainability issues
  • Security Hotspots: Identify potential security vulnerabilities
  • Duplications: Find duplicated code blocks
  • PR Decoration: Inline comments on issues in changed code

Setup (One-Time)

  1. Create SonarCloud Account: Go to sonarcloud.io and sign in with GitHub
  2. Projects are already configured in SonarCloud:
  3. syrf-web - Angular frontend
  4. syrf-api - API service
  5. syrf-project-management - PM service
  6. Add Secrets in GitHub repo settings → Secrets → Actions:
  7. SONAR_TOKEN_WEB - Token for syrf-web project
  8. SONAR_TOKEN_API - Token for syrf-api project
  9. SONAR_TOKEN_PM - Token for syrf-project-management project

Coverage Sources

Language Format Report Path
.NET Cobertura XML coverage/dotnet/**/coverage.cobertura.xml
Angular LCOV coverage/web/lcov.info

Quality Gate Badges

Add to your service README:

Web (Angular):

[![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=syrf-web&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=syrf-web)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=syrf-web&metric=coverage)](https://sonarcloud.io/summary/new_code?id=syrf-web)

API:

[![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=syrf-api&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=syrf-api)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=syrf-api&metric=coverage)](https://sonarcloud.io/summary/new_code?id=syrf-api)

Project Management:

[![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=syrf-project-management&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=syrf-project-management)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=syrf-project-management&metric=coverage)](https://sonarcloud.io/summary/new_code?id=syrf-project-management)

MongoDB Testcontainers fixtures: one client per fixture

MongoDbTestFixture and MongoDbReplicaSetTestFixture (SyRF.Testing.Common) hand out a MongoContext through CreateContext(). MongoContext.Dispose() does not dispose its MongoClient, so a context created per test leaks a connection pool (one mongod thread per connection). About 84 such calls exhausted the CI container's 256-PID cap and killed the shared MongoDB-RS replica set late in Test .NET (#3864).

  • CreateContext() with no argument returns one shared context per fixture; call it freely.
  • CreateContext(resumeRepo) builds a dedicated context. The fixture tracks it and disposes its client when the fixture is disposed, but each one still holds a pool until then; do not call it in a loop.
  • Never new MongoContext(...) in a test without disposing MongoClient yourself.
  • The CI container is capped at 2 GB and 256 PIDs (DockerTestContainerPolicy) whenever SYRF_TEST_JOB_KEY is set. Reproduce locally with that variable set; run the whole project at most once.

Timeouts

Test Suite CI Timeout Rationale
Angular 5 minutes Tests run in ~10s, pnpm install adds overhead
.NET 10 minutes Includes restore, build, and test

.NET Test Projects

Project Tests
SyRF.API.Endpoint.Tests API service tests
SyRF.ProjectManagement.Endpoint.Tests PM service tests
SyRF.AppServices.Tests AppServices library tests
SyRF.ProjectManagement.Core.Tests PM Core library tests
SyRF.WebHostConfig.Common.Tests WebHostConfig library tests

Troubleshooting

Angular Tests Fail in CI but Pass Locally

Common causes:

  1. Missing PDF.js mocks: The vitest-setup.ts includes mocks for PDF.js
  2. Circular dependencies: Some components have placeholder tests due to circular imports
  3. Timing issues: Use fakeAsync and tick for async operations

.NET Tests Fail

Common causes:

  1. Missing dependencies: Run dotnet restore syrf.sln first
  2. Build errors: Run dotnet build syrf.sln before testing
  3. Database dependencies: Tests should mock MongoDB connections

PDF-Agent Integration Tests Time Out During Teardown

Run the affected suite with individual test timings (Docker is required):

DOTNET_PROCESSOR_COUNT=4 SYRF_TEST_JOB_KEY=syrf-local-pdf-retry \
  dotnet test src/services/pdf-agent/SyRF.PdfAgent.Tests/ -c Release \
  --filter 'Category=Integration' --logger 'console;verbosity=normal'

Use a unique SYRF_TEST_JOB_KEY for each concurrent run. It labels that run's containers and applies the same resource caps as CI.

The RabbitMQ/Quartz retry fixture starts hosted services through MassTransit's test harness, not a Generic Host. Its Quartz registration must set AwaitApplicationStarted = false: the fixture's application lifetime never raises ApplicationStarted. Leaving the default enabled lets message assertions pass but makes Quartz shutdown wait for the one-minute harness timeout for every test, exhausting the main integration job's 25-minute budget. Do not fix that by extending the job timeout or shortening the test's assertion timeout.

QuartzRetryFixtureLifecycleTests verifies scheduler startup and shutdown without the host signal or Docker. The real-broker integration suite still verifies scheduled redelivery; production Quartz configuration is unchanged.

Coverage Below Threshold

Fix: Add tests for uncovered code paths. Check src/services/web/coverage/lcov-report/index.html for coverage report.

Viewing Test Results

In GitHub Actions

  1. Go to Actions tab
  2. Select the workflow run
  3. Check test-dotnet or test-web job
  4. Download artifacts for detailed reports

Artifact Contents

Artifact Contents
dotnet-test-results .trx test result files
dotnet-coverage Cobertura XML coverage
web-coverage lcov, cobertura, text coverage
web-test-results JUnit XML test results