Bobcat with TUnit
Bobcat.TUnit lets a TUnit suite publish itself as Bobcat specifications -- the tests keep running on TUnit, and Bobcat renders, reports and tracks them as scenarios.
dotnet add package Bobcat.TUnitNothing is rewritten and the tests keep their own runner. For writing new specifications, see Writing Specifications.
Wire it up
Put [BobcatScenario] on the test class, and name the feature it belongs to:
[BobcatFeature("Booking"), BobcatScenario]
public class BookingSpecs
{
private readonly Dictionary<Guid, string> _appointments = new();
[Test]
public async Task a_proposed_appointment_is_confirmed()
{
// Given a proposed appointment
var id = await Propose();
// When it is confirmed
await Confirm(id);
// Then the appointment is confirmed
await Assert.That(_appointments[id]).IsEqualTo("Confirmed");
}
private Task<Guid> Propose()
{
var id = Guid.NewGuid();
_appointments[id] = "Proposed";
return Task.FromResult(id);
}
private Task Confirm(Guid id)
{
_appointments[id] = "Confirmed";
return Task.CompletedTask;
}
}Run it and the console prints the specification, with the marker comments as its declared steps:
Feature: Booking
═════════════════
a proposed appointment is confirmed OK
─────────────────────────────────────────────
· Given a proposed appointment — declared
· When it is confirmed — declared
· Then the appointment is confirmed — declared
Succeeded with Rights: 0, Wrongs: 0, Errors: 0
Duration: 19ms (steps 0ms)The Then is declared rather than judged because TUnit's own Assert.That is not projected; a SpecAssert.Check inside a step helper, or an opted-in Shouldly assertion, is what puts a judged cell under it.
It works on a single method too, when only part of a class should project. [BobcatSpec] and [BobcatTheory] are xUnit attributes; on TUnit, bind a test to an Event Model slice with [BobcatSlice] beside [BobcatScenario] (see Writing Specifications). Each [Arguments] row records as its own specification (each data row is its own specification).
What the attribute does
Unlike the xUnit adapter, this one implements TUnit's ITestStartEventReceiver and ITestEndEventReceiver rather than deriving from a base attribute. The effect is identical: a scenario opens around each test and closes with the verdict TUnit reported -- a failing test is published as a failure, a skipped one is withdrawn.
[BobcatScenario(Lifecycle = Lifecycle.Acceptance)] (bobcat#545) marks a spec written before its behaviour is finished: a failure is recorded in full as acceptance: failing and TUnit reports the test skipped (OverrideResult), so the build stays green. See not yet passing.
Versions
Built against TUnit.Core 1.74.0. The package takes no test-platform reference; your own TUnit package brings the platform. See Version Compatibility.
What it renders
Everything Bobcat renders is the same whichever runner a test runs on, so all of it applies here too -- a rendered specification on the console, expected/actual cells through SpecAssert, tables and sets from a table literal, and projected Shouldly assertions.
One thing is TUnit-specific. A TUnit suite is run by spec identity through the platform's tree-node filter, and that filter can only name several methods of one class, or one method name across several classes, in a single run. The resident runner refuses a cold command for any other set rather than run tests nobody asked for; a warm command runs any set.
Use this package rather than writing your own
The adapter carries things only Bobcat knows -- the verdict, run-id and BOBCAT_RUN_TAG attribution, and the RunStarted/RunFinished pair, closed once the session's last scenario ends. A hand-rolled adapter that gets the verdict wrong publishes every scenario as a clean pass, including the failing ones, and a suite in that state looks exactly like a correct one. That has happened; see Bobcat with xUnit.net for the full account.
Turning the test into a readable specification
The attribute publishes the test. Marker comments and decorated step helpers are what make it read as a specification -- see Writing Specifications.
See also
- Bobcat with xUnit.net -- the same thing, other runner
- Writing Specifications -- step helpers, marker comments,
SpecAssertand the scenario vocabularies