Skip to content
bitzorcas
中EN

Reference

Fixtures and Seed Data

Choose pure objects, WebApplicationFactory, Testcontainers, and module-owned CSV seeds for deterministic, isolated, replayable test data.

Last updated

A fixture establishes boundaries; seed data establishes repeatable initial facts. Do not make every test share one universal database. Pure domain rules use object builders, HTTP pipelines use WebApplicationFactory, real persistence and messaging use Testcontainers, and platform initialization uses module-owned ISeedStep instances.

Fixture selection matrix

Fact to verifyRecommended fixtureAvoid
Aggregate transitionDirect object/test builderStarting a host
Handler and pure policyExplicit fake port and fixed clockMocking private methods
Middleware, auth, Problem DetailsTestApiFactory / WebApplicationFactoryCalling only the handler
SQL translation, transaction, concurrencyTestcontainers SQL ServerEF InMemory
CAP/Outbox and brokerSQL Server + RabbitMQ containersIn-memory event list
ORM parityOne contract against SqlSugar and EF CoreComparing SQL strings
Package consumptionTemp directory + isolated feed/cacheProduct ProjectReference

Test type determines cost and evidence. Enumerate branches at the cheapest layer and reserve high layers for critical cross-boundary paths.

Current integration-fixture shapes

Non-Docker API tests use TestApiFactory for Shell composition. GoldenUseCaseApiFactory starts SQL Server, RabbitMQ, and a real API Host. Store, repository, Website, CAP, audit, and seeder contracts commonly implement IAsyncLifetime to own containers directly.

tests/BitzOrcas.Integration.Tests/AssemblyInfo.cs disables parallel execution so containers do not compete for Docker resources. Do not re-enable class-level parallelism for superficial speed.

Terminal window
# No Docker: API Shell and cross-layer safe-degradation behavior.
dotnet test tests/BitzOrcas.Integration.Tests \
--configuration Release --filter 'Category!=Docker'
# Docker: run only seeder contracts and avoid unrelated containers.
dotnet test tests/BitzOrcas.Integration.Tests \
--configuration Release \
--filter 'Category=Docker&FullyQualifiedName~Seeders.'

Seed-asset ownership

CSV assets live with the owner module’s Infrastructure assembly. Current assets span Identity, MasterData, Authorization, Menu, PlatformBilling, Catalog, Numbering, and a small Framework compatibility location. One centralized SqlSugar directory no longer represents the system.

Terminal window
# Physical assets; runtime execution still depends on host DI composition.
find src/Platform src/Framework \
-path '*/Seeders/Assets/*.csv' -print | sort
# Inspect step identity, order, scope, and explicit dependencies.
rg -n 'SeedId =>|Order =>|SeedScope =>|DependsOn =>' \
src/Platform src/Framework -g '*SeedStep.cs'

Filename prefixes aid review and align with Order, but SeedRunner schedules registered ISeedStep.Order values. Duplicate SeedId or Order and missing DependsOn fail before execution.

ORM-neutral seed base

The standard base is EntitySetCsvSeedStepBase<TEntity>, not the old CsvSeedStepBase<T>. It reads embedded CSV from the step assembly, queries IEntitySet<TEntity> by stable business key, then inserts or copies owner-managed fields.

public sealed class TestCatalogSeedStep(
IEntitySet<TestCatalogRecord> rows,
CsvSeedReader reader,
ILogger<TestCatalogSeedStep> logger)
: EntitySetCsvSeedStepBase<TestCatalogRecord>(rows, reader, logger)
{
public override int Order => 960;
public override string SeedId => "test_catalog";
public override SeedScope SeedScope => SeedScope.Demo;
protected override string CsvFileName => "960-test_catalog.csv";
// Code survives replay; a generated database Id is not a stable match key.
protected override string[] WhereColumns => [nameof(TestCatalogRecord.Code)];
protected override Expression<Func<TestCatalogRecord, bool>> Match(TestCatalogRecord source)
=> row => row.Code == source.Code;
protected override void Copy(TestCatalogRecord source, TestCatalogRecord target)
{
// Copy CSV-owned values while preserving identity, tenant, and runtime audit data.
target.Name = source.Name;
target.IsEnabled = source.IsEnabled;
}
}

Order 960 is illustrative and must be checked for global uniqueness in the real target composition. An owner chooses and guards its range; copying the number does not create a convention.

Scope and environment

ScopeDevelopment/DemoStaging/ProductionPurpose
GlobalRunRunPlatform-wide language, currency, and region data
TenantRunRunDefault-tenant organization and setting overrides
ProductionSafeRunRunRequired platform definitions and master data
DemoRunSkip automaticallySample catalog, non-production accounts/content

BitzOrcasSeedOptions can disable the runner, set SkipSeedIds, or configure a CSV override root. A test override belongs in a temporary directory and is deleted afterward. Production evidence must not depend on an uncommitted developer-machine CSV.

Deterministic data rules

  • use test-owned TenantId, business keys, and timestamps; never require another test to run first;
  • obtain time from IAppClock or an explicit argument, not a racing UtcNow assertion;
  • record random seeds so failures replay;
  • create and clean each test’s facts, or isolate with a transaction/container;
  • assert business keys and observable results, not exact generated identity numbers;
  • use synchronization barriers for concurrency, not Task.Delay timing guesses;
  • keep real passwords, tokens, licenses, customer data, and machine paths out of CSV.

Testcontainers lifecycle

public sealed class CatalogSqlContractTests : IAsyncLifetime
{
private readonly MsSqlContainer database = new MsSqlBuilder().Build();
public async Task InitializeAsync()
{
// Container start is followed by a query-level readiness check.
await database.StartAsync();
await MsSqlContainerReadiness.WaitForQueryAsync(database.GetConnectionString());
}
// xUnit invokes async disposal after assertion failures, reclaiming the container.
public Task DisposeAsync() => database.DisposeAsync().AsTask();
}

On failure, preserve container logs, readiness stage, and the first business assertion. Do not merely extend a Testcontainers timeout; distinguish image pull, daemon, resources, readiness, and schema failures.

Idempotency and partial-failure tests

A seed step covers first run on empty schema, replay, owner-managed value updates, duplicate business-key rejection, missing dependency rejection, and Demo skipping in production. Parent/child steps also verify DependsOn and foreign-key order.

“Running twice does not throw” is insufficient. After replay, row count remains stable, owner values converge, runtime fields remain intact, and report status/exit code are correct.

Fixture review checklist

  • fixture layer matches the fact being proved;
  • Docker tests carry Category=Docker and enter the right CI shard;
  • no test depends on order, fixed ports, shared users, or historical databases;
  • containers have readiness, cancellation, and deterministic cleanup;
  • SeedId, Order, and DependsOn are valid in the target composition;
  • Global/Tenant/Demo/ProductionSafe scope matches the data;
  • replay, partial failure, and concurrency have assertions;
  • logs and failure messages expose no connection strings or secrets.

Continue with the seed reference, integration tests, and running tests.

100%

Scroll or use controls to zoom · drag when enlarged · double-click for 100% / 200%