Skip to content

Latest commit

 

History

History
310 lines (209 loc) · 9.16 KB

File metadata and controls

310 lines (209 loc) · 9.16 KB

Getting Started

This guide introduces the core concepts of XFTY and demonstrates the most common ways of generating test data.

After reading this guide you should be comfortable:

  • generating records
  • customizing individual fields
  • creating related records
  • understanding Bundles
  • choosing insert modes
  • deciding when relationships should be created

More advanced topics such as implementing Providers and writing custom value expressions are covered in later guides.

InsertMode.Now inserts for real, through whatever IPersistenceGateway you configure (see insert-modes); with none configured it throws rather than silently doing nothing. Everything below uses Mock instead — realistic-looking Ids, nothing persisted — since that's what a unit test usually wants.


Creating Your First Record

The simplest way to use XFTY is to request an object from a Provider.

using Net.NowhereAtAll.Xfty.Core;                 // InsertMode, InsertInclusivity, …
using Net.NowhereAtAll.Xfty.Core.RecordProviders; // RecordProvider, RecordProvider<TRecord>, IRecordProvider
using Net.NowhereAtAll.Xfty.Demo;

DefaultProviderLookup lookup = new();

Contact contact = await new RecordProvider<Contact>(lookup)
    .Supply();

This creates a single Contact. The generic RecordProvider<Contact> returns a typed record — no cast. The non-generic new RecordProvider(typeof(Contact), lookup) is equivalent but its Supply() returns object. Both live in Net.NowhereAtAll.Xfty.Core.RecordProviders.

By default:

  • one object is generated
  • no records are persisted
  • no related records are generated
  • default values are supplied automatically

The returned object is immediately ready for use in your test.


Providers

A Provider is responsible for generating test data for a particular record type.

For example:

  • a Contact Provider knows how to create Contacts
  • an Account Provider knows how to create Accounts
  • a Case Provider knows how to create Cases

Tests never need to know how these objects are constructed. They simply request the object type they need.

Internally, Providers use centrally-defined Master Templates to populate required fields and relationships.


Provider Lookups

A Provider only knows what type of object you want.

A Provider Lookup knows which Provider should be used to generate it.

DefaultProviderLookup lookup = new();

RecordProvider<Contact> provider = new(lookup);

Separating Providers from Provider Lookups lets an application register different Provider implementations without modifying the framework itself.

DefaultProviderLookup (Net.NowhereAtAll.Xfty.Demo) is this port's own starter-kit lookup — a working example to copy and adjust for your project, not a base class to extend. See extend/provider-lookups.


Override Templates

Most tests only care about one or two fields.

Instead of constructing an entire record, provide an Override Template containing only the values relevant to your test.

Contact contact = await new RecordProvider<Contact>(lookup)
    .SetOverrideTemplate(new Contact { FirstName = "Alice", LastName = "Smith" })
    .Supply();

XFTY preserves the supplied values while generating everything else automatically.

For example, if the Master Template specifies a default email address, that value will still be generated.

If the Override Template specifies an email address, the Override Template always wins.


Shorthand Constructors

Three constructor overloads save a call for the most common starting points:

// from a template - derives the record type (and any Provider variant) from it
new RecordProvider(new Contact { FirstName = "Alice" }, lookup);

// from a list of templates - derives the record type from the first
new RecordProvider([new Contact(), new Contact()], lookup);

// from a lookup key - derives the record type from the key and pins that variant
new RecordProvider(LookupKey.Get<Contact>(), lookup);

They are exactly equivalent to the (Type, lookup) constructor followed by SetOverrideTemplate(...) / SetOverrideTemplateList(...) / WithVariant(...). Lookup keys and variants are covered in provider-variants.


Generating Multiple Records

There are two ways to create multiple records.

The simplest is to specify a quantity.

List<Contact> contacts = await new RecordProvider<Contact>(lookup)
    .SetQuantityPerTemplate(5)
    .SupplyList();

This generates five Contacts using the same template.

If each generated record should differ, use an Override Template List instead.

List<Contact> contacts = await new RecordProvider<Contact>(lookup)
    .SetOverrideTemplateList([
        new Contact { FirstName = "Alice" },
        new Contact { FirstName = "Bob" },
    ])
    .SupplyList();

When both a quantity and an Override Template List are supplied, every template is generated the requested number of times.


Creating Related Records

Relationship generation is controlled independently from persistence.

Bundle bundle = await new RecordProvider<Contact>(lookup)
    .SetInsertMode(InsertMode.Mock)
    .SetInclusivity(InsertInclusivity.Required)
    .SupplyBundle();

The resulting Bundle contains both the requested Contacts and any related records generated during the operation.

object contact = bundle.GetList<Contact>(x => x.Id)![0];
object account = bundle.GetList<Contact>(x => x.AccountId)![0];
Bundle
├── Contact
└── Account

The generated Contact automatically references the generated Account.


Understanding Bundles

Bundles are the primary data structure returned by XFTY.

Rather than returning only the requested records, Bundles contain the entire object graph created during generation.

For example, generating a Case may also generate:

Case
├── Account
└── Contact

Bundles make every generated object available without requiring additional lookups.

Lists are extracted using the relationship field that produced them.

List<object> accounts = bundle.GetList<Case>(x => x.AccountId)!;

Nested Bundles can also be traversed.

Bundle? accountBundle = bundle.GetBundle<Case>(x => x.AccountId);

Insert Modes

Generating objects and persisting objects are separate concerns.

XFTY supports five insert modes, plus one orthogonal setting that composes with any of them.

Mode Description
Never Generate records without Ids.
Mock Generate realistic-looking Ids without any persistence.
Now Insert every generated record through the configured IPersistenceGateway. Throws if none is configured.
Later Behaves like Never while documenting that insertion will happen later.
Deferred Generate like Never over many calls, registering everything for a single later flush; see deferred-insert.

.ExcludePrimaryIds() leaves just this call's own primary un-Id'd, whatever mode it's combined with, while every ancestor it needs is still persisted normally - see insert-modes.

For most tests today:

Test type Recommended mode
Unit Test Mock

Because generated mock Ids do not point at real records, tests should never treat a Mock record as if it were persisted.


Relationship Inclusivity

Relationship generation is controlled independently from insertion.

Mode Description
None Create no related records.
Required Create only required relationships.
All Create required and optional relationships.
PreventCascade Create only the first level of relationships.

Required is recommended for most tests.

It produces enough related data for records to be valid without generating unnecessary object graphs.


Which Supply Method Should I Use?

Every Provider ultimately generates a Bundle.

The convenience methods simply extract data from that Bundle.

Method Returns
Supply() First generated record
SupplyList() Primary generated records
SupplyBundle() Entire generated object graph

If your test only needs the requested records, Supply() or SupplyList() are usually sufficient.

If your test needs to inspect related records, use SupplyBundle().


Next Steps

Now that you understand the basic workflow, each feature has its own page — see the feature matrix.

To teach XFTY about a new record type, see extend/providers.

Runnable: RecordProviderIntegrationTest, RecordFactoryTest