Goal
Add a Bobcat.Marten package mirroring the design of Bobcat.Alba — extension methods on IStepContext that look up an IDocumentStore (or IDocumentSession) via a marker resource interface, so fixture step methods can do Marten work without holding direct references.
This is item #2 in CONTEXT.md "Next Steps" and an enabler for the event-sourced samples (BankAccountES, MeetingGroupMonolith, BookingMonolith, several others).
Surface to deliver
namespace Bobcat.Marten;
public interface IMartenResource : ITestResource
{
IDocumentStore DocumentStore { get; }
}
public class MartenResource : IMartenResource { ... }
public class MartenResource<TDocument> : IMartenResource { ... } // optional: typed marker
public static class MartenStepContextExtensions
{
// Document operations
Task<T?> QueryByIdAsync<T>(this IStepContext context, object id, string? resourceName = null);
Task<IReadOnlyList<T>> QueryAllAsync<T>(this IStepContext context, string? resourceName = null);
Task StoreAsync<T>(this IStepContext context, T document, string? resourceName = null);
Task DeleteByIdAsync<T>(this IStepContext context, object id, string? resourceName = null);
// Event sourcing
Task<IReadOnlyList<IEvent>> FetchStreamAsync(this IStepContext context, Guid streamId, string? resourceName = null);
Task<T?> AggregateStreamAsync<T>(this IStepContext context, Guid streamId, string? resourceName = null);
// Hygiene helpers for between-scenario reset (or for dedicated `[Given]` clean steps)
Task CleanAllMartenDataAsync(this IStepContext context, string? resourceName = null);
Task CompletelyRemoveAllAsync(this IStepContext context, string? resourceName = null);
}
IMartenResource.ResetBetweenScenarios() should default to CleanAllMartenDataAsync so spec runs start with a fresh database (overridable per-resource).
Considerations
IDocumentStore lookup should not take a generic constraint — the existing pattern in IAlbaResource (marker interface, no generics) is the cleanest reuse.
- Don't try to abstract over
IQuerySession vs IDocumentSession. The session is short-lived; helpers should open their own session per call (Marten's design encourages this).
- Helpers should accept an optional
tenantId parameter once a sample needs multi-tenancy (defer until a sample drives it).
- Tests: replicate Bobcat.Alba.Tests style (~14 tests there). Cover resource lifecycle,
Reset... semantics, and a couple of step-context lookup paths.
- Marten 8.32+ (the version Bobcat samples will pin to). Use Marten's primary surface; avoid deprecated
MartenLinqQueryable casts (one of the things that bit us in CritterWatch testing this week).
Out of scope
- Combined patterns with Wolverine (tracked sessions + event store assertions). That's the next package,
Bobcat.CritterStack.
Reference
- Existing analog:
src/Bobcat.Alba/AlbaStepContextExtensions.cs and src/Bobcat.Alba/AlbaResource.cs
- Marten testing utilities to wrap:
IDocumentStore.Advanced.Clean.CompletelyRemoveAllAsync, session.LoadAsync<T>(id), session.Events.FetchStreamAsync(streamId)
Goal
Add a
Bobcat.Martenpackage mirroring the design ofBobcat.Alba— extension methods onIStepContextthat look up anIDocumentStore(orIDocumentSession) via a marker resource interface, so fixture step methods can do Marten work without holding direct references.This is item #2 in CONTEXT.md "Next Steps" and an enabler for the event-sourced samples (BankAccountES, MeetingGroupMonolith, BookingMonolith, several others).
Surface to deliver
IMartenResource.ResetBetweenScenarios()should default toCleanAllMartenDataAsyncso spec runs start with a fresh database (overridable per-resource).Considerations
IDocumentStorelookup should not take a generic constraint — the existing pattern inIAlbaResource(marker interface, no generics) is the cleanest reuse.IQuerySessionvsIDocumentSession. The session is short-lived; helpers should open their own session per call (Marten's design encourages this).tenantIdparameter once a sample needs multi-tenancy (defer until a sample drives it).Reset...semantics, and a couple of step-context lookup paths.MartenLinqQueryablecasts (one of the things that bit us in CritterWatch testing this week).Out of scope
Bobcat.CritterStack.Reference
src/Bobcat.Alba/AlbaStepContextExtensions.csandsrc/Bobcat.Alba/AlbaResource.csIDocumentStore.Advanced.Clean.CompletelyRemoveAllAsync,session.LoadAsync<T>(id),session.Events.FetchStreamAsync(streamId)