CodoMetis.TypeKit.Testing 2.3.0

Prefix Reserved
There is a newer version of this package available.
See the version list below for details.
dotnet add package CodoMetis.TypeKit.Testing --version 2.3.0
                    
NuGet\Install-Package CodoMetis.TypeKit.Testing -Version 2.3.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CodoMetis.TypeKit.Testing" Version="2.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CodoMetis.TypeKit.Testing" Version="2.3.0" />
                    
Directory.Packages.props
<PackageReference Include="CodoMetis.TypeKit.Testing" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CodoMetis.TypeKit.Testing --version 2.3.0
                    
#r "nuget: CodoMetis.TypeKit.Testing, 2.3.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CodoMetis.TypeKit.Testing@2.3.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CodoMetis.TypeKit.Testing&version=2.3.0
                    
Install as a Cake Addin
#tool nuget:?package=CodoMetis.TypeKit.Testing&version=2.3.0
                    
Install as a Cake Tool

CodoMetis.TypeKit.Testing

Test assertions for the Option, Result and Patch<T> of CodoMetis.TypeKit. None of these types has a .Value, so your tests reach the content through these assertions. They hand the content back, and when they fail, they say what the outcome was.

dotnet add package CodoMetis.TypeKit.Testing   # the test project
using CodoMetis.TypeKit.Testing;

Email email = Email.Create("ops@example.com").ShouldBeSuccess();   // the value, for the test's own assertions
Email.Create("nobody").ShouldBeError(EmailFault.NoAt);              // this error, or the test fails
EmailFault fault = Email.Create("").ShouldBeError();                // any error, handed back

Email.TryFrom("ops@example.com").ShouldBeSome();
Email.TryFrom("nobody").ShouldBeNone();

A request's Patch<T> field tells you whether the request mentioned it, and the assertions check the same. In this example, OrderPatch has Patch<string> Name, Patch<string> Nickname and Patch<int> Quantity:

OrderPatch patch = JsonSerializer.Deserialize<OrderPatch>("""{"nickname":null,"quantity":2}""", JsonSerializerOptions.Web)!;

int quantity = patch.Quantity.ShouldBeValue();   // the value, handed back
patch.Nickname.ShouldBeNull();                   // a clear
patch.Name.ShouldBeAbsent();                     // not in the document

A fault of several types, such as Fault<OrderFault, EditFault>, has no public content either. ShouldHold(reason) fails unless the fault holds that reason, whichever of its types the reason belongs to. It hands back the Fault<TFault> with its detail and rule intact. A reason of a type the fault isn't made of doesn't compile.

Fault<OrderFault, EditFault> refusal = orders.Rename(7, version: 3).ShouldBeError();
Fault<EditFault> stale = refusal.ShouldHold(EditFault.Stale);   // this fault, or the test fails
string? detail = stale.Detail;                                  // for the test's own assertions

Every assertion also works on a Task or a ValueTask of what it checks. Put await in front, and it waits for the task, then checks what the task returned:

Email email = await signups.RegisterAsync("ops@example.com").ShouldBeSuccess();   // awaited, then asserted
await signups.RegisterAsync("nobody").ShouldBeError(EmailFault.NoAt);
await signups.FindAsync("ops@example.com").ShouldBeNone();                         // a ValueTask too
  • The content comes back. ShouldBeSome(), ShouldBeSuccess(), ShouldBeError() and ShouldBeValue() return the value or the error. Compare it with whatever assertions your tests already use: xUnit's Assert, Shouldly, or another.
  • No test framework needed. A failure throws a TypeKitAssertionException, and every framework reports an exception from a test as its failure. The package depends on CodoMetis.TypeKit alone.
  • A failure says what the outcome was: Expected a success, but the Result<Email, EmailFault> is the error NoAt. It prints the error through its ToString(), which is what you need to see in a test. So an exception's message or a Fault's detail shows up. It never prints a value, just as Option, Result and Patch<T> themselves never do.
  • An uninitialized result fails every assertion. The message says it is a default instance, neither a success nor an error.
  • Await an assertion on a task. It fails your test only through the task it returns, so without await the test passes whatever the task holds. The analyzer reports that line (CMTK0003), in a test method that isn't async too, where C# itself says nothing.
  • A task that throws fails your test with its own exception, as it was thrown, and not as an assertion failure. The assertion never sees an outcome.
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.3.1 361 10/31/2026
2.3.0 410 10/29/2026
2.2.0 741 10/18/2026
2.1.0 1,209 10/15/2026
2.0.0 169 10/14/2026
1.5.1 131 10/11/2026
1.5.0 110 10/11/2026

Added

- AspNetCore: FaultResponse.WithoutFaultName() answers a member without naming it: no fault, no rule and no member description in the problem, so two causes on one status look alike; the document says that status names no fault, and IFaultFeature.NameWithheld tells the host (Faults a client must not tell apart (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.AspNetCore/README.md#faults-a-client-must-not-tell-apart)).
- Result.CatchAsync takes a call that returns a ValueTask, stream.ReadAsync(buffer) without .AsTask(); an async lambda binds as before (An exception as an outcome (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/docs/result.md#an-exception-as-an-outcome)).
- TraverseAsync on Option<T> and Patch<T>: an asynchronous check of an optional input or a patch field, with a token form; nothing is called without a value (Collect many results (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/docs/result.md#collect-many-results)).
- Choose(), Choose(selector), SequenceAsync and TraverseAsync read an IAsyncEnumerable<T>, in order and without a list first; the token reaches the stream, nothing is read after the first error, and the enumerator is disposed however they stop (Chain steps (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit/README.md#chain-steps)).
- Fault<TFault>.Cause: Fault.Of(reason, detail, exception) keeps the exception underneath for your log; equality and ToString() leave it out, and ToHttpResult() hands it to the host as IFaultFeature.Cause and FaultProblem<TFault>.Cause, never into the problem (What failed underneath (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.AspNetCore/README.md#what-failed-underneath)).
- Testing: every assertion works on a Task or ValueTask of what it checks, await service.FindAsync(id).ShouldBeSome(); a task that throws fails the test with its own exception, and an assertion left without await is reported (CMTK0003): through ConfigureAwait or WaitAsync, in a branch of a ?:, ?? or switch, and handed to Task.WhenAll, Task.Run or Task.WhenEach and dropped, or to Task.WhenAny or Task.WaitAny, nested at any depth, or awaited in an async lambda whose catch throws again or can't catch it (CodoMetis.TypeKit.Testing (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.Testing/README.md)).
- OpenAPI: AddTypeKit(typeKit => typeKit.InlineValueObjects = true) describes every value object in place by its underlying type's schema, in every position and in OpenAPI 3.1, 3.0 and 2.0, with no component of its own; closed sets keep theirs, and without the setting the document is unchanged (Describe value objects as plain types (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/docs/aspnet-core.md#describe-value-objects-as-plain-types)).
- Analyzers: CMTK0009 warns at FirstOrNone, SingleOrNone or FirstOrError on a query given a Func variable or a method group, which binds to the IEnumerable<T> overload and reads every row; a lambda at the call or an Expression stays silent (A condition that reads every row (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.Analyzers/README.md#a-condition-that-reads-every-row)).
- Docs: the guides, the package READMEs, the changelog and the API of every run-time package as one site with a menu and search, built from the latest release (caffeinatedcoder.github.io/CodoMetis.TypeKit (https://caffeinatedcoder.github.io/CodoMetis.TypeKit/)).
- Analyzers: a validated value object without Create gets a code fix, Add Create, that writes a Create accepting every value, ready for your rules (Add Create (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.Analyzers/README.md#add-create)).
- Docs: a walkthrough follows one orders service from request to response, saying what changes at each boundary; the service is a sample that CI builds from the packages and runs, and the answers the page states are checked against it (Walkthrough (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/docs/walkthrough.md)).
- Docs: the root README and the Generators README show, folded, the code the generators write for OrderId, Email and a closed set; a test holds each block to what the build emits (Declare a value object (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.Generators/README.md#declare-a-value-object)).
- EF Core: queries, keys and round trips are tested on a real SQL Server, and the README says what differs there from memory: text compared ignoring case, Guids sorted by their last group, decimal(18,2) by default, a TimeSpan under a day, Greatest from compatibility level 160 (On SQL Server (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#on-sql-server)).

Changed

- Analyzers: CMTK0003 now reports four forms that code built on 2.2.0 can contain, so your build can fail with it when you upgrade. They are a Task.WhenAny of Result tasks you don't hold (dropped, awaited, waited for or returned as a plain Task), a dropped Task.WhenEach of them, Wait() on a ?:, ?? or switch of them, and an array of them written in a Task.WaitAll or Task.WaitAny call. Each one loses results that nobody reads: handle them, or discard them with _ =. An awaited Task.WhenAny of tasks you hold is no longer reported (What a new version may change (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.Analyzers/README.md#configuration)).
- EF Core: a default value-object key over ulong gets a temporary value at Add, as a plain ulong key does, where it threw OverflowException on SQL Server and PostgreSQL; a char key configured ValueGeneratedOnAdd() gets a temporary char on SQL Server, not a character of a GUID string. HiLo and your own generator still come first. A compiled model made with --nativeaot, and a key you configure with ValueObjectConverter yourself, still throw (Keys (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#keys)).
- Packages: nuget.org's Release Notes tab and Visual Studio's Updates tab show the version's section of this changelog, as plain text, where they showed a link to the file.
- Changelog: a released version's links name its tag, blob/v2.2.0/, so its notes open the pages as they were released, not as they read on main today.
- Comparison: SuperStrong.Types 1.0.0 joins the value-object table, Thinktecture.Runtime.Extensions is compared at 10.6.0 and LanguageExt at 5.0.0-beta-78, and the page's facts are re-read before every release (How it compares (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/docs/comparison.md)).
- README: introduces TypeKit by example, a one-line OrderId, a validated Email, an Option lookup and a Result chain, and thanks Vogen, which inspired the value objects; every README says what Metalama is where it first names it (CodoMetis.TypeKit (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/README.md#what-you-get)).

Fixed

- Generators: the aspects keep no compilation objects in static fields, which in an IDE that keeps Metalama's process across edits held every earlier compilation in memory.
- Generators: a generated TryParse(string?, IFormatProvider?, out) ends at its return; the unreachable statements after it are gone from the woven code (Reading the generated code (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.Generators/README.md#reading-the-generated-code)).

Full changelog: https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/CHANGELOG.md