Skip to content
Ulf Bourelius edited this page Sep 19, 2025 · 5 revisions

Zentient.Testing β€” Overview & Introduction

Zentient.Testing is a lightweight, opinionated testing toolkit for .NET 8 and .NET 9, designed to make scenario‑driven tests and focused unit‑style integrations easy to write, read, and maintain.

It provides:

  • Scenario factories for arranging dependencies, executing code, and asserting results.
  • A compact mock engine with a fluent Given / ThenReturns / ThenThrows DSL.
  • Result assertions for expressive, chainable verification.
  • A minimal harness for per‑test dependency resolution.

🎯 Purpose & Audience

Audience:
Maintainers, contributors, integration engineers, and advanced adopters.

Purpose:
Document the public API, design intent, and canonical usage patterns for Zentient.Testing, with runnable examples from the repository’s samples/Features tests.


πŸš€ Quick Navigation

Section Description
Getting Started Create and run your first scenario test.
Concepts Core mental model and terminology.
Architecture High‑level component overview and extension points.
Usage Examples Recipes for common patterns.
API Reference (alfa) Public API snapshot for v0.1.0-alfa.
Release Notes / Roadmap What’s in alfa and what’s next.

🧩 Core Public API

From 0.1.0‑alfa.cs:

  • Scenario Factories β€” TestScenario.For, TestScenario.ForHandler (sync/async)
  • Scenario Interface β€” ITestScenario<TInput, TResult> with:
    • Arrange(Action<ITestHarnessBuilder>)
    • ActAsync(TInput, CancellationToken)
    • Assert(Action<IResultAssertions<TResult>>)
  • Harness Builder β€” ITestHarnessBuilder with:
    • WithDependency<T>(instance)
    • WithMock<T>(configure)
    • WithMock<T>(configure, out IMockVerifier<T>)
    • Replace<T>(instance)
  • Mocking β€” IMockBuilder<T> and IMockVerifier<T>
  • Assertions β€” IResultAssertions<TResult> with NotBeNull, HaveValue, WithProperty, AndAlso

πŸ“š Canonical Usage Patterns

All examples below are taken from the samples/Features tests in the repository.

1. Arrange with a Concrete Dependency

var scenario = TestScenario.ForHandler<SimpleHandler, string, string>(
    (h, input, ct) => Task.FromResult(h.Handle(input))
);

scenario.Arrange(b => b.WithDependency<ISimpleService>(new SimpleServiceImpl()));

var result = await scenario.ActAsync("in");
Xunit.Assert.Equal("impl:in", result);

2. Arrange with a Mock

using Zentient.Testing.Internal; // for It.IsAny<T>()

var scenario = TestScenario.ForHandler<SimpleHandler, string, string>(
    (h, input, ct) => Task.FromResult(h.Handle(input))
);

scenario.Arrange(b => b.WithMock<ISimpleService>(mb =>
    mb.Given(s => s.Do(It.IsAny<string>())).ThenReturns("mocked")
));

var result = await scenario.ActAsync("in");
Xunit.Assert.Equal("mocked", result);

3. Mock with Verification

IMockVerifier<ISimpleService> verifier = null!;

var scenario = TestScenario.ForHandler<SimpleHandler, string, string>(
    (h, input, ct) => Task.FromResult(h.Handle(input))
);

await scenario
    .Arrange(b => b.WithMock<ISimpleService>(
        mb => mb.Given(s => s.Do(It.IsAny<string>())).ThenReturns("done"),
        out verifier
    ))
    .ActAsync("hello");

verifier.ShouldHaveBeenCalled(nameof(ISimpleService.Do));
verifier.ShouldHaveBeenCalledTimes(nameof(ISimpleService.Do), 1);

4. Async Handler with Mocked Async Dependency

var scenario = TestScenario.ForHandler<AsyncHandler, string, string>(
    (h, input, ct) => h.HandleAsync(input, ct)
);

scenario.Arrange(b => b.WithMock<IWorker>(mb =>
    mb.Given(x => x.WorkAsync(It.IsAny<string>()))
      .ThenReturns(Task.FromResult("ok"))
));

var result = await scenario.ActAsync("input");
Xunit.Assert.Equal("ok", result);

5. Result Assertions with Chaining

var scenario = TestScenario.ForHandler<ResultHandler, int, ResultDto>(
    (h, input, ct) => Task.FromResult(h.Handle(input))
);

scenario.Arrange(b => b.WithDependency<IResultProvider>(new ResultProvider()));

var result = await scenario.ActAsync(7);

scenario.Assert(a => {
    a.NotBeNull();
    a.AndAlso.WithProperty(r => r.Value, 7);
});

πŸ”„ Choosing the Right TestScenario Factory

Factory Method When to Use Example
For<TInput, TResult> You want full control over the act step and will manually resolve services from the harness. (h, input, ct) => ...
ForHandler<THandler, TInput, TResult> (async) You have a handler type with an async method and want the harness to resolve it automatically. (handler, input, ct) => handler.ProcessAsync(input, ct)
ForHandler<THandler, TInput, TResult> (sync) Same as above, but for synchronous methods. (handler, input) => handler.Process(input)

πŸ›  Best Practices

  • Keep scenarios small and deterministic β€” one input, one primary assertion.
  • Use WithDependency for stubs/fakes, WithMock for interaction verification.
  • Prefer keeping examples in samples/ so they compile against the repo and stay current.

πŸ“š Next Steps

πŸ“š Zentient.Testing Docs

Quick links to all major sections of the documentation.


πŸ“„ Overview

  • Landing Page β€” What Zentient.Testing is and why it exists.
  • Release Notes β€” Changes in the current version.
  • Roadmap β€” Planned features and milestones.

🧠 Core Concepts

πŸš€ Getting Started

πŸ“‘ Reference

πŸ›  Development


πŸ’‘ Tip: Use the Architecture page as your hub β€” it links to Getting Started, Usage Examples, API Reference, and Concepts.

Clone this wiki locally