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:
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)¶
- Create SonarCloud Account: Go to sonarcloud.io and sign in with GitHub
- Projects are already configured in SonarCloud:
syrf-web- Angular frontendsyrf-api- API servicesyrf-project-management- PM service- Add Secrets in GitHub repo settings → Secrets → Actions:
SONAR_TOKEN_WEB- Token for syrf-web projectSONAR_TOKEN_API- Token for syrf-api projectSONAR_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):
[](https://sonarcloud.io/summary/new_code?id=syrf-web)
[](https://sonarcloud.io/summary/new_code?id=syrf-web)
API:
[](https://sonarcloud.io/summary/new_code?id=syrf-api)
[](https://sonarcloud.io/summary/new_code?id=syrf-api)
Project Management:
[](https://sonarcloud.io/summary/new_code?id=syrf-project-management)
[](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 disposingMongoClientyourself. - The CI container is capped at 2 GB and 256 PIDs (
DockerTestContainerPolicy) wheneverSYRF_TEST_JOB_KEYis 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:
- Missing PDF.js mocks: The vitest-setup.ts includes mocks for PDF.js
- Circular dependencies: Some components have placeholder tests due to circular imports
- Timing issues: Use
fakeAsyncandtickfor async operations
.NET Tests Fail¶
Common causes:
- Missing dependencies: Run
dotnet restore syrf.slnfirst - Build errors: Run
dotnet build syrf.slnbefore testing - 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¶
- Go to Actions tab
- Select the workflow run
- Check test-dotnet or test-web job
- 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 |
Related Documentation¶
- CI/CD Workflow Overview - Complete CI/CD pipeline
- Web Service README - Angular development
- Test Workflows Locally - Using
actfor local testing