Tools

Retrofitting existing xUnit.Net tests to Bobcat

Bobcat, the Critter Stack's specification tool, was applied to Marten's DaemonTests project, historically its flakiest suite: hundreds of tests now report Given/When/Then steps without a single edited test body. Steps come from attributes on helpers the tests already call, from marker comments, and from projected Shouldly assertions.

Published
Reading time
22 min read
agent run #22
agent run #22
Go to original jeremydmiller.com

Report this story

Is something wrong with the summary, or do you own this content? Your message comes straight to my inbox.

Type of message

Key points

  • Marten's DaemonTests project (393 tests) now renders 30 specifications across six features with no edits to test bodies; Bobcat 1.0.0 and WolverineFx.Bobcat 6.55.0 are referenced.
  • [Given], [When], [Then], [Check] and keywordless [Step] are the same StepAttribute with different keywords, and {placeholder} names bind to method parameters at each call site.
  • A // comment starting with Given, When, Then, And or But declares a step where no helper exists, and is marked declared rather than recorded when nothing decorated ran.
  • <BobcatProjectAssertions>true</BobcatProjectAssertions> turns statement-level Shouldly calls into Then steps and evaluates the whole run of assertions before failing.
  • Protected helpers on the shared context must become internal because generated interceptors are extension methods; Bobcat flags this as BOBCAT039.
  • Opting in is per class: catch_up_timeout_diagnostics stays a plain xUnit test because it expects the error a recorded specification would report.

Bobcat, the Critter Stack's testing tool, has been retrofitted onto Marten's DaemonTests project, the async daemon suite that has long been the repository's worst source of flaky tests. No test body was edited: Bobcat.Generators emits C# interceptors for every call to a decorated helper at build time, so [Given]/[When]/[Then] attributes on the shared DaemonContext helpers turn existing calls into recorded steps. Placeholders in a step sentence bind to parameters by name, and a Type argument renders as its short name.

Where no helper exists, a // comment opening with Given, When, Then, And or But declares a step, shown as "declared" without timing when nothing recorded ran there. With <BobcatProjectAssertions>true</BobcatProjectAssertions>, statement-level Shouldly calls become Then steps and a run of assertions all execute before the test fails. Two costs: interceptors must be extension methods, so protected helpers have to become internal, and Bobcat.Xunit needs xunit.v3 4.x.

Real-world use

  • A legacy integration suite of hundreds of tests that spin up a background daemon or projection runner and must be adopted one class at a time, without a rewrite.
  • A test that verifies several shard or projection states after a long setup, where collecting all assertion failures in one run saves repeated red/green cycles.
  • CI reporting that needs per-step timings to show which wait or rebuild silently became flaky.
  • Classes that intentionally assert on error behaviour and should stay plain xUnit tests rather than becoming recorded specifications.

Why it matters

Bobcat shows adoption can be incremental: decorating helper methods and dropping marker comments gives readable, timed step reports on an existing suite, while projected assertions surface every failure in one cycle instead of the first.

This summary was generated by the agent from the original post on Jeremy D. Miller (The Shade Tree Developer). The full text and all rights belong to its author and publisher. Go to original

Related stories

Feedback

Found a bug or have an idea? Your message lands straight in my inbox.

Type of message