AlbaResource— non-generic, factory-based (Func<Task<IAlbaHost>>), implementsIHostResourceAlbaResource<TProgram>— generic, usesAlbaHost.For<TProgram>(), implementsIHostResource- Both expose
IAlbaHost AlbaHostforScenario()calls andIHost Hostfor service access AlbaStepContextExtensions—ScenarioAsync,PostJsonAsync,GetJsonAsync,PutJsonAsync,DeleteAsync,LastScenarioResultAlbaAuthExtensions—ScenarioWithClaimsAsyncfor per-scenario auth overrides (usesWithClaimsingular, notWithClaims)- 14 tests passing in
Bobcat.Alba.Tests
- No
WolverineResource— deliberately eliminated. Users bring their ownIHost. - Pure extension methods on
IStepContextthat resolveIHostviaIHostResource WolverineStepContextExtensions—InvokeMessageAndWaitAsync,SendMessageAndWaitAsync,ExecuteAndWaitAsync,TrackActivity- Works against ANY
IHostResource(HostResource, AlbaResource, or custom) - Wolverine tracking API is in
Wolverine.Trackingnamespace, mainWolverineFxpackage
IHostResource : ITestResource— marker interface withIHost Host { get; }HostResource— factory-based (Func<Task<IHost>>), takes optional reset callbackHostResource<TProgram>— usesHost.CreateApplicationBuilder(), TProgram is a lookup markerHostResourceExtensions—GetHost(name?)andGetHostService<T>(name?)onIStepContext- C#'s
classgeneric constraint accepts interface types, soGetResource<IHostResource>()works
- 11 sample projects copied from
~/code/critterstacksamplesintosamples/ - Feature files and fixture classes created for all 11 projects
- Not yet wired up to compile/run — the feature files and fixtures exist but the projects don't have Bobcat references, runner setup, or test project structure
- PR #7 attempted full wiring (101/101 passing) but was on a worktree branch that conflicts with main
- Fixtures are loose — users compose their own, likely using multiple AlbaHosts
- Extension methods on
IStepContextkeep things flexible - State tracked as fixture instance fields (new instance per scenario = automatic reset)
- Wolverine/Marten extensions don't own the host — they just find one via
IHostResource - Both
HostResourceandAlbaResourceimplementIHostResource - Enables Wolverine extensions to work against an Alba-managed host seamlessly
- Storyteller had
Context.State— a per-spec dictionary keyed by type or type+name - Bobcat currently relies on fixture instance fields for step-to-step state
AlbaResource.LastResulttracks the most recentIScenarioResult- Cross-fixture state bag is a future Bobcat core addition if needed after more real-world usage
Bobcat (core)
├── IHostResource, HostResource, HostResource<T>
├── ITestResource, TestSuite, BobcatRunner
├── Fixture, Attributes, Source Generator
│
Bobcat.Alba
├── AlbaResource, AlbaResource<T> (implements IHostResource)
├── AlbaStepContextExtensions, AlbaAuthExtensions
│
Bobcat.Wolverine
├── WolverineStepContextExtensions (extension methods only, targets IHostResource)
│
Bobcat.Marten (TODO)
├── MartenStepContextExtensions
├── CleanAllMartenDataAsync, QueryByIdAsync, FetchStreamAsync, etc.
│
Bobcat.CritterStack (TODO — assumes Wolverine + Marten together)
├── Combined patterns: tracked sessions + event store assertions
├── Aggregate handler testing, projection wait helpers
| Project | Feature File | Fixture | Wired Up | Tests Pass |
|---|---|---|---|---|
| CqrsMinimalApi | ✅ | ✅ | ✅ | ✅ (5/5) |
| CleanArchitectureTodos | ✅ | ✅ | ❌ | ❌ |
| BankAccountES | ✅ | ✅ | ❌ | ❌ |
| EcommerceMicroservices | ✅ | ✅ | ❌ | ❌ |
| OutboxDemo | ✅ | ✅ | ❌ | ❌ |
| MoreSpeakers | ✅ | ✅ | ❌ | ❌ |
| BookingMonolith | ✅ | ✅ | ❌ | ❌ |
| EcommerceModularMonolith | ✅ | ✅ | ❌ | ❌ |
| MeetingGroupMonolith | ✅ | ✅ | ❌ | ❌ |
| PaymentsMonolith | ✅ | ✅ | ❌ | ❌ |
| ProjectManagement | ✅ | ✅ | ❌ | ❌ |
- No Bobcat project references — sample .csproj files don't reference Bobcat, Bobcat.Alba, or Bobcat.Generators
- No runner setup — samples need a
Program.cs(or separate test project) that callsBobcatRunner.Run()withAlbaResourceregistered onTestSuite - Fixture API mismatch — generated fixtures use extension methods (
PostJsonAsync,GetJsonAsync) that needIStepContextpassed in, but the source generator doesn't injectIStepContextas a parameter to step methods. Fixtures need to access context viathis.Contextproperty instead. - PostgreSQL required — all sample projects use Wolverine/Marten which need a running PostgreSQL instance. Docker-compose from critterstacksamples provides this on port 5432.
[Check]attribute requires a string argument (the step text), not bare like[Fact]
- The Bobcat source generator produces a
{Feature}_Feature.g.csfile - Steps are matched by Cucumber Expression patterns in
[Given]/[When]/[Then]attributes - The generator passes parsed arguments (string, int, etc.) to fixture methods but does NOT pass
IStepContext - Fixtures must use
this.Context(theFixture.Contextproperty, which isIStepContext?) to access resources AlbaHost.For<Program>()with Wolverine crashes with a native error if PostgreSQL isn't running — Wolverine's startup is not graceful without a database
The built-in demo runs 3 features (Calculator, Inventory, Invoicing) with 8 scenarios.
- 7/8 pass (1 deliberate failure to demo set verification error reporting)
- Set verification tables render beautifully with OK/FAIL/MISSING/EXTRA status columns
- Spectre.Console rendering with checkmarks, timing, and summary counts
src/Bobcat/Runtime/ITestResource.cs—Name,Start(),ResetBetweenScenarios(),DisposeAsync()src/Bobcat/Runtime/IHostResource.cs— extends ITestResource withIHost Hostsrc/Bobcat/Runtime/HostResource.cs— both generic and non-generic implementationssrc/Bobcat/Runtime/TestSuite.cs— resource registry and lifecyclesrc/Bobcat/Engine/IStepContext.cs—GetResource<T>(),GetService<T>(),Log(),AttachDiagnostic()src/Bobcat/Engine/ExecutionContext.cs—SpecExecutionContextimplementationsrc/Bobcat/Fixture.cs— base class withContextproperty,SetUp(),TearDown()src/Bobcat/Runtime/BobcatRunner.cs— CLI entry point,Run()andScanForFeatures()
src/Bobcat.Alba/AlbaResource.cs— both generic and non-genericsrc/Bobcat.Alba/AlbaStepContextExtensions.cs— HTTP helper extensionssrc/Bobcat.Alba/AlbaAuthExtensions.cs— auth helper extensions
src/Bobcat.Wolverine/WolverineStepContextExtensions.cs— tracked session extensions
AlbaHost.For<TProgram>(params IAlbaExtension[])— async factoryAlbaHost.For(WebApplicationBuilder, Action<WebApplication>, params IAlbaExtension[])— builder+routes factory (good for test projects without a real Program)IAlbaHost.Scenario(Action<Scenario>)→Task<IScenarioResult>IScenarioResult.ReadAsJsonAsync<T>()— deserialize responses.Get.Url(url),s.Post.Json(body).ToUrl(url),s.Put.Json(body).ToUrl(url),s.Delete.Url(url)s.StatusCodeShouldBeOk()(200),s.StatusCodeShouldBe(int),s.StatusCodeShouldBeSuccess()(2xx)s.WithClaim(type, value)— singular, notWithClaims- Alba 8.5.0, targets net8.0/net9.0/net10.0
BeforeEach/AfterEachhooks are additive (appended to a list), not last-wins
- Namespace:
Wolverine.Tracking host.InvokeMessageAndWaitAsync(message)→Task<ITrackedSession>host.InvokeMessageAndWaitAsync<T>(message)→Task<(ITrackedSession, T)>host.SendMessageAndWaitAsync(message)→Task<ITrackedSession>host.ExecuteAndWaitAsync(Func<IMessageContext, Task>)→Task<ITrackedSession>- Session assertions:
session.Sent.SingleMessage<T>(),session.Executed.SingleHandler<T>() - In main
WolverineFxpackage (no separate testing package)
- Wire up sample specs to actually compile and run (need Bobcat refs, runner setup, PostgreSQL)
- Build
Bobcat.Martenpackage (MartenStepContextExtensions) - Build
Bobcat.CritterStackpackage (combined Wolverine + Marten) - Revisit state management pattern after more real-world usage
- Consider adding Storyteller-style
Context.Statebag to Bobcat core
mainHEAD:3ac44fa("CqrsMinimalApi spec passes 5/5 — refactor to RESTful Guid contract (Path A)")- Working tree clean, in sync with
origin/main— no uncommitted work to flush. - All in-flight Bobcat work has been parked and the remaining items are tracked as GitHub issues.
7e6f4f1Wire CqrsMinimalApi sample to the BobcatRunner end-to-end — first sample that actually compiles, runs against a live PostgreSQL, and exercises the Alba + Wolverine step-context extensions through the full BobcatRunner CLI path.3ac44faCqrsMinimalApi spec passes 5/5 — refactor to RESTful Guid contract (Path A) was the sample-side change to expose stable Guid resource ids over a clean REST surface so the spec could assert against id-stable URIs instead of in-memory positional refs.
A spurious native crash on Apple-silicon hosts was traced to two collisions when the sample app and SpecsRunner shared a process:
- TFM mismatch — sample built
net9.0while Bobcat builtnet10.0. The runtime loaded the wrong AOT-compiled native bits and aborted in PAL. - Top-level-statements
Programcollision — both projects emitted a synthesizedProgramclass at the root namespace. Fix: bumped the sample TFM to match Bobcat's, and converted SpecsRunner to an explicitMainmethod in a named class so there's no synthesizedProgramto collide.
This is no longer an active blocker. If a similar PAL crash resurfaces, those two angles are the right first checks.
The CritterWatch docker-compose was switched from postgis/postgis:17-3.4 (amd64-only
manifest) to pgvector/pgvector:pg17 (multi-arch). Bobcat samples that run against the
shared local Postgres benefit from the same change — exit-code-126 on the postgres
container was masking what looked like Bobcat-specific failures.
- #8 Wire up remaining 10 sample projects to BobcatRunner (template established by
CqrsMinimalApi). The CqrsMinimalApi case study under
samples/CqrsMinimalApi/is the reference pattern: project file references for Bobcat / Bobcat.Alba / Bobcat.Generators, a SpecsRunner withBobcatRunner.Run(), and anAlbaResource<Program>registration on theTestSuite. - #9 Build
Bobcat.Marten—MartenStepContextExtensionsanalogous to the Wolverine extensions:CleanAllMartenDataAsync,QueryByIdAsync,FetchStreamAsync, etc., resolvingIDocumentStorevia the registeredIHostResource. - #10 Build
Bobcat.CritterStack— assumes Wolverine + Marten together. Combined helpers for tracked sessions + event-store assertions, aggregate-handler testing, projection wait helpers. - #11 Improve diagnostics for sample-wiring footguns + document the playbook so the next person wiring a sample doesn't have to rediscover the five blockers from the Sample Projects Status table above.
Bring this file plus the open issue numbers (#8 / #9 / #10 / #11). The bobcat repo itself is at a clean stopping point, so resuming is "pick an issue, read the relevant section here for context, go." No mid-flight branches to clean up.