Testing
Flo uses NUnit + Moq for backend tests, Jest (via jest-preset-angular) for frontend unit tests, and Playwright for end-to-end tests. All suites run in GitHub Actions on the self-hosted Zoro runner.
Backend Testing (NUnit + Moq)
Quick Start
# Run all tests
dotnet test Flo.BE.Tests
# Run with coverage report (coverlet + ReportGenerator)
./run-coverage.sh --open
Stats
| Metric | Value |
|---|---|
| Framework | NUnit with Moq |
| Test methods | 5,039 [Test] methods across 382 test files |
| Test fixtures | 404 [TestFixture] classes |
| Parameterized cases | 654 [TestCase] attributes |
| Coverage | Local only — ./run-coverage.sh (coverlet + ReportGenerator); CI runs the suite without a coverage gate |
| Excludes | Framework assemblies, migrations, Program, ServicesRegistration (Flo.BE.Tests/coverlet.runsettings) |
Test Structure
Flo.BE.Tests/
├── Services/ # Service-level unit tests (largest suite)
├── BoundedContexts/ # Domain bounded contexts (Immobili, EInvoicing, ...)
├── Controllers/ # Controller tests
├── Integration/ # Integration tests
├── Newsletter/
├── WebAnalytics/
├── Security/
├── Media/
├── Validation/
└── ... # Helpers, Quotas, Localization, ...
Test Pattern
[TestFixture]
public class BookingServiceTests
{
private Mock<IBookingRepository> _mockRepo;
private BookingService _service;
[SetUp]
public void SetUp()
{
_mockRepo = new Mock<IBookingRepository>();
_service = new BookingService(_mockRepo.Object);
}
[Test]
public async Task CreateBooking_ValidRequest_ReturnsSuccess()
{
// Arrange
var dto = new CreateBookingDto(1, DateTime.Now, "Notes");
// Act
var result = await _service.CreateAsync(dto);
// Assert
Assert.That(result, Is.Not.Null);
_mockRepo.Verify(x => x.AddAsync(It.IsAny<Booking>()), Times.Once);
}
[TestCase("valid@email.com", true)]
[TestCase("", false)]
public void ValidateEmail_ReturnsExpected(string email, bool expected)
{
var result = _service.IsValidEmail(email);
Assert.That(result, Is.EqualTo(expected));
}
}
Rules
- Use NUnit (
[Test],[SetUp],[TearDown],Assert.That()) — never xUnit - Use Moq for mocking dependencies
- All backend services require unit tests
- Follow Arrange-Act-Assert pattern
Frontend Testing
Unit Tests (Jest)
cd Flo.FE
npm test # jest --config jest.config.ts --watchman=false
npm run test:coverage # Jest with coverage collection
- Jest 30 with
jest-preset-angular(jsdom environment); config inFlo.FE/jest.config.ts pretestruns the E2E runner lifecycle checks (node --test e2e/local-port-pair.test.mjs)- Jest ignores
/e2e/, so Playwright specs never leak into the unit suite - All components use
OnPushchange detection, which must be accounted for in tests
E2E Tests (Playwright)
cd Flo.FE
npm run e2e # headless Chromium
npm run e2e:headed
npm run e2e:ui # Playwright UI mode
playwright.config.ts:testDir: e2e/tests, Chromium project,baseURLfromFE_URL(defaulthttp://localhost:14201)- The full-stack specs live in
e2e/tests/ciand run underplaywright.ci.config.ts(own web server + fresh seed) npm run e2e:ci(node e2e/run-ci.mjs) bootstraps the whole stack: on Windows it starts a throwaway Postgres container and the backend, installs Chromium if missing, and cleans everything up at the end
CI/CD
CI Jobs (ci.yml)
| Job | What it runs |
|---|---|
sdd | Validates the SDD corpus and generated indexes/release notes (CLI) |
gitflow-lineage | Verifies the branch base against gitflow rules |
build-frontend | npm ci, Jest (npm test -- --ci --detectOpenHandles --forceExit), production build (build-prod-standard) |
e2e | Runner lifecycle checks, Postgres 17 + Mailpit services, backend in Debug (auto-migrate + seed), Playwright container v1.62.1, full-stack suite via scripts/run-e2e-container.sh |
test-cli | CLI unit tests (npm test in cli/) |
build-backend | .NET 10: dotnet restore --locked-mode, Release build, dotnet test Flo.BE.Tests --no-build, best-effort PostgreSQL-gated DynamicSchemaPostgres tests, dotnet format on changed files |
error-codes-sync | Checks that frontend i18n covers all backend error codes |
All jobs run on the self-hosted runner [self-hosted, linux, x64, flo-build] ("Zoro"), with checkout from the Zoro mirror and workspace cleanup after every job.
Windows Compatibility
The test tiers (backend, frontend, e2e) must stay green on Windows too (see docs/specs/2026-10-02-windows-test-compatibility.md):
- NUnit fixes are platform-consistency fixes (log directory resolution, absolute filesystem paths in image URLs, POSIX lease paths, line-ending and symlink/junction handling in tests)
node e2e/run-ci.mjsis self-sufficient on Windows: it starts a throwaway Postgres container and the backend, installs the Playwright browser if missing, and cleans up afterwards- Runner lifecycle tests normalize line endings; the Linux/CI-only container wrapper tests skip explicitly on Windows
i18n Check
The i18n-check.yml workflow runs node check-translations.js --ci on push/PR to master/develop and daily at 06:00 UTC. It reports missing or mismatched keys between it.json and en.json as a non-blocking PR comment and a job summary.
Docker Build
The docker-publish.yml workflow runs on push to master/develop:
- Builds and pushes the image to GitHub Container Registry (
ghcr.io/team-ledges/flo) with provenance and SBOM attestations - Tags:
latest(master),develop-latest(develop), plus branch name and<branch>-<short-sha>for all builds