CodoMetis.TypeKit.Testing
2.3.1
Prefix Reserved
dotnet add package CodoMetis.TypeKit.Testing --version 2.3.1
NuGet\Install-Package CodoMetis.TypeKit.Testing -Version 2.3.1
<PackageReference Include="CodoMetis.TypeKit.Testing" Version="2.3.1" />
<PackageVersion Include="CodoMetis.TypeKit.Testing" Version="2.3.1" />
<PackageReference Include="CodoMetis.TypeKit.Testing" />
paket add CodoMetis.TypeKit.Testing --version 2.3.1
#r "nuget: CodoMetis.TypeKit.Testing, 2.3.1"
#:package CodoMetis.TypeKit.Testing@2.3.1
#addin nuget:?package=CodoMetis.TypeKit.Testing&version=2.3.1
#tool nuget:?package=CodoMetis.TypeKit.Testing&version=2.3.1
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()andShouldBeValue()return the value or the error. Compare it with whatever assertions your tests already use: xUnit'sAssert, 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 onCodoMetis.TypeKitalone. - A failure says what the outcome was:
Expected a success, but the Result<Email, EmailFault> is the error NoAt.It prints the error through itsToString(), which is what you need to see in a test. So an exception's message or aFault's detail shows up. It never prints a value, just asOption,ResultandPatch<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
awaitthe test passes whatever the task holds. The analyzer reports that line (CMTK0003), in a test method that isn'tasynctoo, 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 | Versions 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. |
-
net10.0
- CodoMetis.TypeKit (>= 2.3.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Changed
- Packages: the public surface is checked against 2.3.0's, so nothing 2.3.0 ships is removed or changed in 2.3.1.
- Analyzers: CMTK0005 and CMTK0006 now warn for a type parameter bound by a value object beside new(), as in where T : IValue<int>, new(), which CMTK0001 already reported: new T[n] and a T field nothing sets. In a build that treats warnings as errors, that can report code that built before (CMTK0005 (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.1/src/CodoMetis.TypeKit.Analyzers/README.md#cmtk0005)).
- Generators: a generated member you wrote where a value object reaches it now fails the build (CMTK1011, CMTK1008 for comparison), so your build can fail when you upgrade: an explicit implementation such as IFormattable.ToString in an interface a value object or closed set implements, one in a base record from another project, a base record's public MinValue or MaxValue, which stood in for the generated one, and a base record's Create beside your own. Remove it, don't implement that interface, or declare the marker on the value object itself. A base record's public Value, Parse, ToString(string?, IFormatProvider?) and the like, and IConvertible through an interface, now fail with a CMTK error rather than a Metalama crash (Write your own members (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.1/src/CodoMetis.TypeKit.Generators/README.md#write-your-own-members)).
- Generators: an Equals a value object declares for one of its base records' types, such as Equals(DeepBase?), now fails the build (CMTK1011), so your build can fail when you upgrade. A HashSet<DeepBase> compared by it while == compared the underlying value; normalise values in Create instead (Equality (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.1/src/CodoMetis.TypeKit.Generators/README.md#equality)).
- Generators: a CompareTo(TSelf), CompareTo(object), TryFrom or Revalidate a value object inherits from a base record now fails the build (CMTK1008, CMTK1011), so your build can fail when you upgrade. It stood in for the generated member: an inherited CompareTo made < overflow the stack and an EF Core query order by the column, and an inherited TryFrom need not run Create. Declare it on the value object (Write your own members (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.1/src/CodoMetis.TypeKit.Generators/README.md#write-your-own-members)).
- Generators: a closed set over a base class that declares a member the generators add now fails the build with CMTK1011 naming it, where it failed with a Metalama error that named nothing you wrote: its Parse, TryParse, Match or All, and an IParsable<TSelf> it implements. A base class's ToString() that isn't sealed now fails the build too (CMTK1011), so your build can fail when you upgrade: the generated ToString() replaced it without a word. Declare ToString() in the set, or seal the base's, which is then kept (Closed sets (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.1/src/CodoMetis.TypeKit.Generators/README.md#closed-sets)).
- Generators: IValueObject<TSelf, T>, IValueObjectMaterializer<TSelf, T> or IPlainValueObject<TSelf, T> declared through an interface of yours, such as an IEntityId<TSelf> : IValueObject<TSelf, Guid>, or by a base record now fails with CMTK1011 naming that interface, where it failed with a Metalama error (LAMA0512) that named nothing you wrote. To constrain generic code, use IValueObject<TSelf, T> itself (Write your own members (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.1/src/CodoMetis.TypeKit.Generators/README.md#write-your-own-members)).
- EF Core: Min, Max, Order, OrderDescending or OrderBy(x => x) over a value object with its own CompareTo, cast to object first, as in Select(o => (object)o.Priority).Max(), or read back through ??, a member of a projection, a grouping's key, a join or a subquery's First(), as in Select(o => new { Key = (object)o.Priority }).OrderBy(a => a.Key), now throws as the same query without the cast does. EF dropped the cast and answered the column's order, the opposite of memory's for a descending CompareTo (Queries (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.1/src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#queries)).
- Analyzers: CMTK0006 now warns when a constructor sets a member only on another object it creates, new Order(...) { Id = id } or other with { Id = id }, which it took as setting its own. In a build that treats warnings as errors, that can report code that built before (CMTK0006 (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.1/src/CodoMetis.TypeKit.Analyzers/README.md#cmtk0006)).
- EF Core: a query filter that orders values your context holds without a lambda, as in HasQueryFilter(o => o.Priority == Limits.Max()), builds again; EF works it out in memory by your CompareTo. With a lambda, EF orders those values in the database, and the filter still fails (Queries (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.1/src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#queries)).
- Analyzers: CMTK0005 now also warns at Array.CreateInstance(typeof(OrderId), n), Array.CreateInstanceFromArrayType(typeof(OrderId[]), n) and ArrayPool<OrderId>.Rent(n), which create default slots as new OrderId[n] does. In a build that treats warnings as errors, that can report code that built before (CMTK0005 (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.1/src/CodoMetis.TypeKit.Analyzers/README.md#cmtk0005)).
- Generators: an overload of a generated member that C# tells apart by its type parameters, or by a parameter that is ref, in or out where the generated one's isn't (or the other way round), is now kept beside the generated member, on a value object, a closed set or a base of either, where the build failed with CMTK1011: a Parse<TFormat>(string, IFormatProvider?), a From(in int), a TryParse whose last parameter isn't out (Write your own members (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.1/src/CodoMetis.TypeKit.Generators/README.md#write-your-own-members)).
- Generators: a generic ToString<TFormat>(), or a generic or by-in CompareTo(TSelf), is no longer taken for the one you may write: a value object printed what a record prints (Level { Value = 3 }), a closed set its type name, and the ordering was marked hand-written, so EF Core refused to order by it. A generic or by-reference overload of TryFrom, Revalidate, CompareTo(object), Equals, GetHashCode or PrintMembers is kept beside the generated member, where the build failed with CMTK1011 or CMTK1008 (Write your own members (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.1/src/CodoMetis.TypeKit.Generators/README.md#write-your-own-members)).
- Generators: a member of a base record or of a closed set's base class that the type can't see, a private one such as a private const int MaxValue, doesn't count as its own: it stands in for no generated member, so the checks above leave it alone and the type is generated as in 2.3.0 (Write your own members (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.1/src/CodoMetis.TypeKit.Generators/README.md#write-your-own-members)).
Fixed
- Analyzers: CMTK0005 no longer reports an array with a constant zero in any one dimension, such as new OrderId[0, count], or a zero written as 0L or, in a stackalloc, (byte)0: neither holds an element (CMTK0005 (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.1/src/CodoMetis.TypeKit.Analyzers/README.md#cmtk0005)).
- Analyzers: CMTK0006 no longer reports an explicit interface implementation that every constructor assigns through a cast, ((IHasId)this).Id = id (CMTK0006 (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.1/src/CodoMetis.TypeKit.Analyzers/README.md#cmtk0006)).
- OpenAPI: each element of a list, an array or a dictionary of Patch<T> is described as a patch property is, the argument's schema admitting null, where it admitted anything. A nullable Patch<T>? is its argument's schema admitting null, where it was an empty required component; System.Text.Json reads a null there as a null patch, as it reads a property left out (Use it in an endpoint and in the document (https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.1/docs/merge-patches.md#use-it-in-an-endpoint-and-in-the-document)).
Full changelog: https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/CHANGELOG.md