Skip to main content

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​

MetricValue
FrameworkNUnit with Moq
Test methods5,039 [Test] methods across 382 test files
Test fixtures404 [TestFixture] classes
Parameterized cases654 [TestCase] attributes
CoverageLocal only — ./run-coverage.sh (coverlet + ReportGenerator); CI runs the suite without a coverage gate
ExcludesFramework 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 in Flo.FE/jest.config.ts
  • pretest runs 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 OnPush change 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, baseURL from FE_URL (default http://localhost:14201)
  • The full-stack specs live in e2e/tests/ci and run under playwright.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)​

JobWhat it runs
sddValidates the SDD corpus and generated indexes/release notes (CLI)
gitflow-lineageVerifies the branch base against gitflow rules
build-frontendnpm ci, Jest (npm test -- --ci --detectOpenHandles --forceExit), production build (build-prod-standard)
e2eRunner 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-cliCLI 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-syncChecks 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.mjs is 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