Author unit tests for the AG-UI .NET SDK (the *.UnitTests projects), following the SDK's serialization and compatibility conventions. USE FOR: adding unit-test coverage for a new type/method in AGUI.Abstractions/Formatting/Protobuf/Client/Server, event serialization round-trips, JsonDocument property-name assertions, backward-compatibility fixtures against TypeScript JSON, protobuf codec round-trips, client builder/handler tests, server ChatResponseUpdate conversion tests, SSE formatter tests. DO NOT USE FOR: HTTP pipeline / WebApplicationFactory end-to-end tests (use the agui-dotnet-integration-tests skill), cross-language TS↔C# server-parity tests (use the cross-language test skill).
76
93%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Conventions for the tests/*.UnitTests projects. Mined from sdks/dotnet/AGENTS.md
and existing tests. An agent gets these wrong by default: asserting on deserialized
objects (misses naming bugs), comparing full JSON strings (fragile), using reflection,
or skipping the FixtureLoader compatibility pattern.
All commands run from sdks/dotnet/.
| Project | Focus |
|---|---|
tests/AGUI.Abstractions.UnitTests/ | Event serialization round-trips; backward compat vs TypeScript fixtures (Compatibility/) |
tests/AGUI.Protobuf.UnitTests/ | Protobuf codec round-trips; JsonElement↔protobuf Value conversion |
tests/AGUI.Client.UnitTests/ | Client builders, content-negotiation handler, transport, protocol rules |
tests/AGUI.Server.UnitTests/ | ChatResponseUpdate → AG-UI event conversion |
tests/AGUI.Formatting.UnitTests/ | SSE event-stream formatter (read/write, media type) |
Run one project: dotnet test tests/AGUI.Abstractions.UnitTests/
Events/ subfolders).
Compatibility tests go in the Compatibility/ subfolder.{TypeUnderTest}Test (RunStartedEventTest, ToolCallBuilderTest).
Compatibility class: {Category}CompatibilityTest (RunEventsCompatibilityTest).public sealed class, one class per file, file name matches type.[Fact] for single cases, [Theory] + [InlineData] for parameterized cases.AGUIJsonSerializerContext.Default.{Type} —
never JsonSerializer.Serialize<object>(...) or hand-rolled options.Serialize via the source-gen context, parse with JsonDocument, assert each camelCase
property name AND the type discriminator. This is what catches [JsonPropertyName] bugs.
[Fact]
public void Serialization_RoundTrips()
{
var evt = new RunStartedEvent { ThreadId = "t1", RunId = "r1", Timestamp = 1234567890 };
var json = JsonSerializer.Serialize(evt, AGUIJsonSerializerContext.Default.RunStartedEvent);
using var doc = JsonDocument.Parse(json);
Assert.Equal("RUN_STARTED", doc.RootElement.GetProperty("type").GetString());
Assert.Equal("t1", doc.RootElement.GetProperty("threadId").GetString());
Assert.Equal("r1", doc.RootElement.GetProperty("runId").GetString());
Assert.Equal(1234567890, doc.RootElement.GetProperty("timestamp").GetInt64());
}Compatibility/Fixtures/*.json are produced by the TypeScript reference implementation.
Deserialize them into .NET types and assert the values — this catches wire-format drift.
Load arrays with FixtureLoader; write one test method per event shape.
public sealed class RunEventsCompatibilityTest
{
private readonly JsonElement[] _fixtures = FixtureLoader.LoadFixture("run-events.json");
[Fact]
public void RunStartedEvent_DeserializesFromTypeScriptPayload()
{
var evt = FixtureLoader.DeserializeAsBaseEvent(_fixtures[0]);
var typed = Assert.IsType<RunStartedEvent>(evt);
Assert.Equal("thread-1234", typed.ThreadId);
Assert.Equal("run-5678", typed.RunId);
}
}Adding a fixture: place the TS-produced JSON array under Compatibility/Fixtures/
(embedded resource), then index into the loaded array per shape.
Encode→decode and assert typed values. Compare JsonElement payloads with
JsonTestHelpers.AssertEqual (deep-equals), not string compare. ProtoValueConverter
tests verify JsonElement↔Value for each ValueKind.
var result = RoundTrip(new RunStartedEvent { ThreadId = "thread-1", RunId = "run-1" });
Assert.Equal("thread-1", result.ThreadId);
JsonTestHelpers.AssertEqual(expectedElement, result.RawEvent!.Value);Drive the builder/handler through its real API and assert the produced MEAI types.
ToolCallBuilder/TextMessageBuilder: feed events, flush, assert FunctionCallContent,
ConversationId, ResponseId. AGUIEventStreamHandler: assert content-negotiation
Accept header ordering via a test inner handler. Standard xunit assertions.
Convert a ChatResponseUpdate stream to AG-UI events and assert the sequence with
Assert.Collection, type-checking each event and its fields (e.g. run lifecycle wraps
content with RunStartedEvent/RunFinishedEvent).
Assert MediaType, CanRead(contentType) (use [Theory]/[InlineData] for case and
null/empty handling), and that WriteAsync produces the data: {json} shape per event.
[JsonPropertyName] mistakes. Parse the JSON with
JsonDocument and assert the literal camelCase property names + type discriminator.JsonTestHelpers.AssertEqual for payloads).AGUIJsonSerializerContext.Default.{Type} so tests exercise the real AOT context.34c3e0c
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.