XFTY is a declarative test data factory for C#.
Instead of manually constructing complete object graphs for every test, you
describe only the values your test actually cares about. XFTY supplies
sensible defaults, automatically creates related records, and either mocks
persistence entirely or actually inserts through a pluggable
IPersistenceGateway β the same Provider definitions serve a pure in-memory
unit test and a real database integration test.
By centralizing test data definitions, XFTY dramatically reduces boilerplate and makes tests more resilient to changing validation rules, required fields, and evolving business logic.
dotnet add package XftyAdd whichever opt-in packages you want the same way. Only Xfty itself is
required β everything else is independent and opt-in, grouped below by
what each one actually does:
| Package | What it does | NuGet | Tests |
|---|---|---|---|
| Core | |||
| Xfty | Declarative generation, relationships, persistence seam | ||
Persistence β IPersistenceGateway implementations | |||
| Xfty.EntityFrameworkCore | Real, database-backed persistence via EF Core | ||
| Xfty.EntityFramework6 | Real, database-backed persistence via classic EF6 | ||
| Xfty.VectorDatabases.Qdrant π§ͺ | PREVIEW: persistence via Qdrant's own client directly | ||
| Xfty.VectorDatabases.MicrosoftExtensionsVectorData π§ͺ | PREVIEW: persistence via any Microsoft.Extensions.VectorData connector | ||
Value Generation β bundled IValueExpressions | |||
| Xfty.Bogus | Realistic fake data - names, emails, addresses, paragraphs | ||
| Xfty.VectorDatabases | A random-vector value expression for an embedding field | ||
| Auto-Population Pairings | |||
| Xfty.AutoFixture | Pairs XFTY with AutoFixture, both directions | ||
| Xfty.AutoBogus | Pairs XFTY with AutoBogus, both directions | ||
| Test-Framework & Language Integration | |||
| Xfty.Xunit | [IsolatesSharedAncestor] xUnit attribute | ||
| Xfty.FSharpAsync | Async<'T> wrappers for F#'s original async { } workflow | ||
π§ͺ = preview proof-of-concept, versioned 0.x-preview rather than
1.0.0-beta.* - read its own README before depending on it for anything
beyond the question it was built to answer.
Every "Tests" badge above points at the same single CI workflow β this repo builds and tests every package together, not each in isolation, so there's no per-package signal distinct from the whole solution's.
As a project grows, so does the amount of code required simply to create valid test data.
A Contact requires an Account. Later, a validation rule requires
additional Account fields. Eventually another related type becomes
mandatory. Over time, hundreds or even thousands of tests can end up
duplicating nearly identical setup code.
XFTY centralizes that knowledge.
Instead of every test knowing how to construct a valid object graph, Providers define that logic once, allowing individual tests to override only the fields they actually care about.
The result is test code that is:
- shorter
- easier to read
- easier to maintain
- more resilient to application changes
- Declarative test data generation, described once per Provider
- Automatic relationship generation β required, optional, shared ancestors, self-referential cycles guarded automatically
- Context-aware values: a field derived from a sibling, a generated
ancestor, or (once the graph exists) a generated child, with a loud error
on a mis-ordered read instead of a silent wrong
null - Per-call relationship control (
IncludeOptional,ExcludeRelationship) without touching a Provider's own definition - Lambda-based field access throughout (
x => x.Field, not a barePropertyInfoornameof(...))
- Real persistence through
IPersistenceGatewayβXfty.EntityFrameworkCoreships an EF Core implementation, proven against SQLite and a real Postgres container β or mock Ids with no database touched at all - Deferred and depth-batched insert: build a graph across several calls, then insert it once, in dependency order, across mixed record types
- Suitable for both isolated unit tests and real-database integration tests, with the same Provider definitions
- Extensible Provider architecture β a Provider implements
IRecordProviderdirectly, holding itsMasterTemplateas a field and delegatingCreateBundletoRecordFactory(composition, no base class to inherit) - Multi-variant Providers (
FlavouredLookupKey,DiscriminatorLookupKey) β resolve a different Provider for the same type by an arbitrary predicate or field value
- Optional add-on packages for common conveniences core
Xftydoesn't bundle - none is a dependency of coreXftyitself. See the package table up top for the full roster. - Targets
netstandard2.0/net8.0/net10.0β .NET Framework 4.6.1+, Mono/Xamarin, and older .NET Core all work, not just current .NET
See How XFTY compares for how this stacks up against AutoFixture, Bogus, and similar libraries.
Generate a Contact with sensible defaults:
DefaultProviderLookup lookup = new();
Contact contact = (Contact)await new RecordProvider(typeof(Contact), lookup)
.Supply();Override only the fields your test actually cares about:
Contact contact = (Contact)await new RecordProvider(typeof(Contact), lookup)
.Put<Contact>(x => x.FirstName, "Alice")
.SetInsertMode(InsertMode.Mock)
.Supply();Generate complete related object graphs:
Bundle bundle = await new RecordProvider(typeof(Contact), lookup)
.SetInsertMode(InsertMode.Mock)
.SetInclusivity(InsertInclusivity.All)
.SupplyBundle();
Contact contact = (Contact)bundle.GetList<Contact>(x => x.Id)![0];
Account account = (Account)bundle.GetList<Contact>(x => x.AccountId)![0];
Assert.Equal(account.Id, contact.AccountId);Full documentation is in docs/, organised by audience:
| I want to⦠| Go to |
|---|---|
| Use XFTY to write tests | docs/use/ β start with getting-started |
| Teach XFTY about my own record types | docs/extend/ |
| Work on XFTY itself | docs/contribute/ β architecture |
| Look something up | docs/reference/ β api-cheatsheet, known-issues |
| See what's built / planned | docs/roadmap/ |
XFTY was designed around a simple idea:
Tests should describe only what makes them unique.
Everything else should be generated automatically.
Rather than scattering test data throughout an entire codebase, XFTY moves that knowledge into reusable Providers that declaratively describe valid records and their relationships.
The framework then constructs those object graphs automatically, allowing test code to remain focused on the behaviour being tested rather than on setup.
XFTY is not a general-purpose "fill in an object" library like AutoFixture, and it doesn't ship realistic fake-data generators like Bogus. What it does that they don't:
- Generates a related graph β required/optional relationships, shared ancestors deduplicated across many children, self-referential cycles guarded automatically β not one object at a time.
- Has an actual opinion about persistence: the same Provider definitions
run as a pure in-memory
Mockin a unit test, or insert for real throughIPersistenceGatewayin an integration test, with no rewrite. - Resolves a different Provider variant for the same type by a runtime key or predicate, and supports context-aware values β a field derived from a sibling, ancestor, or generated child, with a loud guard against reading one that hasn't been generated yet.
Core Xfty has no built-in realistic fake-data generation (Xfty.Bogus is
an optional add-on for that) and no auto-population by default - every
field a Provider cares about is declared, not guessed. Xfty.AutoFixture
and Xfty.AutoBogus are optional pairings for the fields a Provider
doesn't care about (or for pointing the tool's own generation at a
Provider directly) - neither changes core Xfty's own philosophy. See
docs/reference/comparison.md for the full,
unvarnished comparison against AutoFixture, Bogus, AutoBogus, and NBuilder,
including where XFTY is a worse fit than any of them.
Recently landed (see the CHANGELOG for the full detail, including everything since the 1.0.0-beta.1 tag):
Xfty.BogusandXfty.VectorDatabasesβ optional packages for realistic fake data and vector-embedding fields, without adding either dependency to coreXftyXfty.AutoFixtureβ pairs XFTY with AutoFixture both directions: pointfixture.Create<T>()at a registeredRecordProvider, and/or let AutoFixture fill in whatever fields a Provider's Master Template left unsetXfty.AutoBogusβ the same pairing for AutoBogus (AutoFixture-style auto-population plus Bogus's realistic generators), completing the trifecta: XFTY now pairs with AutoFixture, Bogus, and AutoBogusXfty.Xunitβ[IsolatesSharedAncestor], resettingSharedAncestorbefore/after a test class or method automaticallyXfty.FSharpAsyncβAsync<'T>wrappers for F# code still built onasync { }rather than the newertask { }, which needs no wrapper at allXfty.EntityFramework6β the sameIPersistenceGatewayconvenience asXfty.EntityFrameworkCore, for a project on classic EF6 (System.Data.Entity.DbContext) rather than EF Core
DiscriminatorLookupKeyβ resolving a Provider by a field's value- Lambda-based field access across the whole public API
- Typed
RecordProvider<TRecord>/ChildProvider<TChild>wrappers β no cast at theSupply()call site, plus aMasterTemplate<TRecord>-style object-initializer indexer - Real persistence via
IPersistenceGateway(Xfty.EntityFrameworkCore, proven against SQLite and a real Postgres container) - Persistence is fully
asyncend to end β everySupply/SupplyList/SupplyBundlecall, and everything reachable from it, is now genuinelyTask-based, matching how every real backing store (EF Core, a vector database client, a network call) already works underneath
- A full sweep to a from-scratch, idiomatic C# port with no remaining Salesforce-specific surface
- A real, fixed thread-safety issue in
SharedAncestorunder concurrent test execution (xUnit's default; this repo's own suite had opted out) - Core
Xftynow multi-targetsnetstandard2.0;net8.0;net10.0, reaching .NET Framework 4.6.1+/Mono/Xamarin as well as modern .NET, verified via a dedicatednet472test project (netstandard2.0isn't itself runnable)
The full status table β built, not-ported, and open ideas under consideration (embedded/denormalized document relationships) β is docs/roadmap/README.md.
Contributions, bug reports, feature requests, and discussions are welcome.
If you would like to contribute:
- Open an issue to discuss proposed changes.
- Keep Provider implementations declarative whenever possible.
- Preserve backwards compatibility unless a compelling reason exists not to.
- Prefer simplicity and readability over additional abstraction.
This project is released under the MIT License.