Class FakeRepository<TAggregate, TId>
In-memory fake repository for testing aggregates. Provides a simple in-memory store with domain event tracking.
public class FakeRepository<TAggregate, TId> where TAggregate : Aggregate<TId> where TId : notnull
Type Parameters
TAggregateThe aggregate type.
TIdThe aggregate ID type.
- Inheritance
-
FakeRepository<TAggregate, TId>
- Inherited Members
- Extension Methods
Remarks
Two persistence surfaces are exposed and they are intentional opposites:
- Setup surface — Add(TAggregate), Remove(TAggregate),
RemoveByIdAsync(TId, CancellationToken). Mirror the production
RepositoryBase<TAggregate, TId>staging API so the sameIRepositorycontract works in handlers and in tests. Use these to seed the store and to exercise the same code path the handler will run against EF. - Result-shape surface — SaveAsync(TAggregate, CancellationToken),
DeleteAsync(TId, CancellationToken). Return Result directly so tests that
specifically assert on conflict-handling or not-found-result behavior have a
concrete Result to assert on. They are not part of the production
RepositoryBasesurface and should not appear on productionIRepositorycontracts.
See cookbook Recipe 16 — Unit of work in handlers: Add staging vs immediate SaveAsync
for the full guidance on which method to call from where.
Properties
Count
Gets the count of stored aggregates.
public int Count { get; }
Property Value
PublishedEvents
Gets the list of domain events published by saved aggregates.
public IReadOnlyList<IDomainEvent> PublishedEvents { get; }
Property Value
Methods
Add(TAggregate)
Stages a new aggregate for insertion into the in-memory store, mirroring
RepositoryBase<TAggregate, TId>.Add. Use this from handlers (and from
test setup that exercises handlers) so the same void Add(T) surface works
in both the EF and fake implementations of an IRepository contract.
public void Add(TAggregate aggregate)
Parameters
aggregateTAggregateThe aggregate to stage.
Remarks
The fake has no separate commit boundary — Add immediately makes the
aggregate visible to subsequent FindByIdAsync/WhereAsync calls and
captures its uncommitted domain events into PublishedEvents.
Unique constraints registered with WithUniqueConstraint(Func<TAggregate, object?>) are
enforced eagerly here: a violation throws InvalidOperationException
rather than returning Error.Conflict. The reason is that
Add is a setup affordance — failures usually mean the test itself is
wrong, and a loud throw points to the offending call site immediately. To test
production conflict handling, call SaveAsync(TAggregate, CancellationToken) instead and assert on
the returned Result.
Clear()
Clears all stored aggregates and published events.
public void Clear()
DeleteAsync(TId, CancellationToken)
Deletes an aggregate by its ID.
public Task<Result<Unit>> DeleteAsync(TId id, CancellationToken cancellationToken = default)
Parameters
idTIdThe aggregate ID.
cancellationTokenCancellationTokenCancellation token.
Returns
- Task<Result<Unit>>
A Result<TValue> with Unit indicating success or Error.NotFound.
Exists(TId)
Checks if an aggregate with the specified ID exists.
public bool Exists(TId id)
Parameters
idTIdThe aggregate ID.
Returns
- bool
True if the aggregate exists, false otherwise.
FindAsync(Func<TAggregate, bool>)
Finds the first aggregate matching the predicate, returning None if no match.
Use in test repository adapters for custom query methods (e.g., FindByEmailAsync).
public Task<Maybe<TAggregate>> FindAsync(Func<TAggregate, bool> predicate)
Parameters
Returns
FindByIdAsync(TId, CancellationToken)
Finds an aggregate by its ID, returning Maybe if not found.
public Task<Maybe<TAggregate>> FindByIdAsync(TId id, CancellationToken cancellationToken = default)
Parameters
idTIdThe aggregate ID.
cancellationTokenCancellationTokenCancellation token.
Returns
Get(TId)
Gets an aggregate by ID without wrapping in Result.
public TAggregate? Get(TId id)
Parameters
idTIdThe aggregate ID.
Returns
- TAggregate
The aggregate or null if not found.
GetAll()
Gets all stored aggregates.
public IEnumerable<TAggregate> GetAll()
Returns
- IEnumerable<TAggregate>
All aggregates in the repository.
GetByIdAsync(TId, CancellationToken)
Gets an aggregate by its ID.
public Task<Result<TAggregate>> GetByIdAsync(TId id, CancellationToken cancellationToken = default)
Parameters
idTIdThe aggregate ID.
cancellationTokenCancellationTokenCancellation token.
Returns
- Task<Result<TAggregate>>
A Result containing the aggregate or an Error.NotFound.
Remove(TAggregate)
Stages an aggregate for deletion from the in-memory store, mirroring
RepositoryBase<TAggregate, TId>.Remove. No-op if the aggregate is not
in the store (matching the EF semantics where the change tracker accepts
the call without verifying database existence).
public void Remove(TAggregate aggregate)
Parameters
aggregateTAggregateThe aggregate to remove.
RemoveByIdAsync(TId, CancellationToken)
Looks up an aggregate by ID and removes it. Returns Error.NotFound
if the aggregate does not exist. Mirrors
RepositoryBase<TAggregate, TId>.RemoveByIdAsync.
public Task<Result<Unit>> RemoveByIdAsync(TId id, CancellationToken cancellationToken = default)
Parameters
idTIdThe aggregate ID to remove.
cancellationTokenCancellationTokenCancellation token (ignored — operation is synchronous).
Returns
- Task<Result<Unit>>
A Result<TValue> with Unit indicating success or not-found failure.
SaveAsync(TAggregate, CancellationToken)
Saves an aggregate and captures its domain events.
public Task<Result<Unit>> SaveAsync(TAggregate aggregate, CancellationToken cancellationToken = default)
Parameters
aggregateTAggregateThe aggregate to save.
cancellationTokenCancellationTokenCancellation token.
Returns
- Task<Result<Unit>>
A Result<TValue> with Unit indicating success or failure.
WhereAsync(Func<TAggregate, bool>)
Returns all aggregates matching the predicate.
Use in test repository adapters for custom query methods (e.g., GetByCustomerIdAsync).
public Task<IReadOnlyList<TAggregate>> WhereAsync(Func<TAggregate, bool> predicate)
Parameters
Returns
- Task<IReadOnlyList<TAggregate>>
A list of matching aggregates.
WhereAsync(Specification<TAggregate>)
Returns all aggregates matching the specification.
Use in test repository adapters for specification-based queries (e.g., GetOverdueOrdersAsync).
public Task<IReadOnlyList<TAggregate>> WhereAsync(Specification<TAggregate> specification)
Parameters
specificationSpecification<TAggregate>The specification to evaluate.
Returns
- Task<IReadOnlyList<TAggregate>>
A list of matching aggregates.
WithUniqueConstraint(Func<TAggregate, object?>)
Adds a unique constraint on the specified property. When SaveAsync(TAggregate, CancellationToken) is called, the repository checks that no other aggregate (with a different ID) has the same value for this property. Returns an Error.Conflict on violation.
public FakeRepository<TAggregate, TId> WithUniqueConstraint(Func<TAggregate, object?> propertySelector)
Parameters
Returns
- FakeRepository<TAggregate, TId>
This repository for fluent chaining.
Examples
var repo = new FakeRepository<Customer, CustomerId>()
.WithUniqueConstraint(c => c.Email);