Test a slice
NetForge ships two test projects on xUnit v3 + Shouldly + NSubstitute. Both have a _Template folder mirroring the copy-me pattern.
Unit tests (pure, fast)
Copy tests/Tests.Unit/Features/_Template. Unit tests cover validators, mappers, permission matching, and paging math — no host, no database:
public class CreateProjectValidatorTests
{
[Fact]
public void Rejects_empty_name()
{
var result = new CreateProjectValidator().Validate(new CreateProjectRequest("", null));
result.IsValid.ShouldBeFalse();
}
}Integration tests (the real pipeline)
Copy tests/Tests.Integration/Features/_Template, rename Template → your domain, point it at your route + permission constants, and drop the Skip. Integration tests run the real pipeline through WebApplicationFactory<Program> against a throwaway SQLite database, with a header-driven auth handler:
[Collection(IntegrationCollection.Name)]
public class ProjectsEndpointsTests(CustomWebApplicationFactory factory)
{
[Fact]
public async Task Create_then_get_round_trips()
{
var client = factory.CreateAuthenticatedClient(permissions: [ProjectPermissions.Create, ProjectPermissions.Read]);
var created = await client.PostAsJsonAsync("/api/projects", new { name = "Apollo" });
created.StatusCode.ShouldBe(HttpStatusCode.Created);
}
}CreateAuthenticatedClient(permissions: [...]) mints a principal with exactly the permissions a scenario needs — so you can assert 401 (no client), 403 (wrong permissions), and 200/201 (granted) in three lines.
Run them
dotnet test NetForge.slnx # unit + integration
dotnet test tests/Tests.Unit # unit only (fastest)