Skip to content

Build Bobcat.Marten package — step-context extensions for Marten in specs #9

@jeremydmiller

Description

@jeremydmiller

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions