This manual explains how to use Observer's .NET-facing provider library.
It is written for projects that want C#-native test authoring while still exposing deterministic provider contracts to Observer.
If you want the fastest path to the .NET provider model, start in starter/ and run:
make list
make inventory
cat tests.inv
make run
make verifyThat shows the whole path in order:
- raw provider host discovery
- derived canonical inventory
- the exact execution contract Observer will run against
- a real suite execution with the human console
- hash and JSONL verification against checked-in expected artifacts
If your application already owns its CLI, use starter-embedded/ instead. That path keeps normal application behavior outside an explicit observe routing point.
If you want a normal consumer-shaped .NET app rather than an SDK-local example, see ../../examples/dotnet-consumer/.
This library is the .NET-facing micro-SDK for Observer's provider contract.
It lets .NET authors write tests with a human-first surface:
Spec.Describe(...)Spec.Test(...)Spec.It(...)Spec.Expect(...)ctx.Observe().Metric/Vector/Tag
Underneath that surface, Observer still enforces its own rules:
- deterministic collection
- explicit canonical identity
- duplicate validation
- bounded observation
- standard provider host transport for
list,run, andobserve
using Observer.Dotnet;
var tests = Spec.CollectTests(() =>
{
Spec.Describe("math", () =>
{
Spec.Test("adds two numbers", ctx =>
{
ctx.Stdout("ok\n");
Spec.Expect(2 + 3).ToBe(5);
});
});
});If Spec.Id(...) is omitted, Observer derives the canonical identity from suite path plus test title.
If a test needs a refactor-stable identity, give it an explicit Spec.Id(...).
Each test receives one context object.
The common author operations are:
ctx.Stdout(...)ctx.Stderr(...)ctx.Fail(...)ctx.Observe()
The first-cut matcher surface includes:
Spec.Expect(value).ToBe(expected)Spec.Expect(value).ToEqual(expected)Spec.Expect(value).ToContain(expected)Spec.Expect(value).ToMatch(pattern)Spec.Expect(value).ToBeTruthy()Spec.Expect(value).ToBeFalsy()Spec.Expect(value).Not.ToBe(...)
The .NET library owns the standard provider host transport.
That means the host surface is:
listrun --target <target> --timeout-ms <u32>observe --target <target> --timeout-ms <u32>
The public SDK entrypoints are:
Spec.HostMain(...)Spec.HostDispatch(...)Spec.HostDispatchEmbedded(...)
If a .NET application already owns Main(), it can expose an embedded observe namespace. See examples/HostEmbedExample/ and starter-embedded/.
The runnable starter in starter/ shows the real adoption path:
- write ordinary .NET code under test
- author Observer tests in C# with
Spec.* - expose those tests through a small provider host executable
- derive canonical inventory through
observer derive-inventory - run a suite against that inventory
- verify canonical hashes and report output against checked-in snapshots
The failing companion in starter-failure/ keeps the same flow but preserves one intentional failure so the deterministic failure report path stays easy to inspect.
The runnable starter in starter-embedded/ shows the application-owned CLI path:
- keep normal application behavior in
Main() - route
observe ...throughSpec.HostDispatchEmbedded(...) - configure Observer with
args = ["observe"] - derive canonical inventory through
observer derive-inventory - run the same suite shape against the embedded provider boundary
- verify canonical hashes and report output against checked-in snapshots
The SDK carries a self-test project:
dotnet run --project lib/dotnet/tests/Observer.Dotnet.SelfTest/Observer.Dotnet.SelfTest.csprojThe install story is the package metadata in Observer.Dotnet.csproj, which supports dotnet pack when needed.
For local adoption inside a .NET solution, dotnet add reference path/to/Observer.Dotnet.csproj is the shortest path.