FirstChanceGuard.Xunit
1.0.0
dotnet add package FirstChanceGuard.Xunit --version 1.0.0
NuGet\Install-Package FirstChanceGuard.Xunit -Version 1.0.0
<PackageReference Include="FirstChanceGuard.Xunit" Version="1.0.0" />
<PackageVersion Include="FirstChanceGuard.Xunit" Version="1.0.0" />
<PackageReference Include="FirstChanceGuard.Xunit" />
paket add FirstChanceGuard.Xunit --version 1.0.0
#r "nuget: FirstChanceGuard.Xunit, 1.0.0"
#:package FirstChanceGuard.Xunit@1.0.0
#addin nuget:?package=FirstChanceGuard.Xunit&version=1.0.0
#tool nuget:?package=FirstChanceGuard.Xunit&version=1.0.0
FirstChanceGuard.Xunit
Fail any xUnit v3 test that throws an exception on a path that was supposed to just work.
Your test suite is green. Run it under the debugger and it crawls, and nobody can say why. Somewhere in your code a method throws, catches its own exception, and keeps going. The code works and the test passes, so the throw never shows up where you would look. The debugger sees it. Every exception raises a first-chance notification the moment it happens, before any catch runs, and an attached debugger handles each one. Put that throw-and-catch in a loop that loads a few thousand rows and a green suite that should take seconds takes minutes. The same cost shows up when you debug the app itself.
Most developers have never gone looking for first-chance exceptions. FirstChanceGuard.Xunit watches for them while your tests run and fails the test that caused an unexpected one, so the hidden cost becomes a red test with a name and a line number.
Install
<PackageReference Include="FirstChanceGuard.Xunit" Version="0.1.0-*" />
Depends only on xunit.v3.extensibility.core. Needs .NET 10 and xUnit v3.
Arm the handler, then turn the guard on
Two steps. Arming attaches the handler that records throws. The attribute turns a recorded throw into a failed test. You need both, and the handler has to be armed before any test runs.
// 1. Arm before any test runs (no warm-up needed).
[assembly: Xunit.v3.TestPipelineStartup(typeof(FirstChanceGuard.Xunit.FirstChanceGuardPipelineStartup))]
// 2. Watch every test in the assembly.
[assembly: FirstChanceExceptionGuard]
Attach [FirstChanceExceptionGuard] to a class or a method instead when you want narrower coverage.
Turn it on early and there is a second payoff. Every happy-path throw fails a test, so exceptions used as control flow cannot quietly take root. You either stop throwing on the normal path or mark the throw as expected on purpose.
When you need a warm-up
Some libraries throw-and-catch on first use, during one-time static setup. bUnit's first render triggers AngleSharp's encoding probe, for instance. Arm from a [ModuleInitializer] and run the warm-up first, so that one-time noise happens before the handler goes live:
using System.Runtime.CompilerServices;
using FirstChanceGuard.Xunit;
internal static class FirstChanceGuardSetup
{
[ModuleInitializer]
public static void Init() => FirstChanceExceptionGuardAttribute.Arm(static () =>
{
using var context = new BunitContext();
context.Render(builder => builder.AddMarkupContent(0, "<p>warm-up</p>"));
});
}
Arm(preWarm, typeof(SomeExpectedException)) adds suite-wide exemptions. OperationCanceledException is exempt already.
Opt out the tests that throw on purpose
[Fact]
[FirstChanceExceptionsExpected] // may throw anything
public void Parsing_garbage_throws() { /* ... */ }
[Fact]
[FirstChanceExceptionsExpected(typeof(FormatException))] // only FormatException; anything else still fails
public void Parsing_reports_a_format_error() { /* ... */ }
Works on a method or a whole class. A method-level list wins over a class-level one, and the annotation doubles as documentation of which paths throw by design.
Measure one operation
Want to prove a single call throws nothing on its happy path, without arming the whole suite? FirstChanceExceptions.CountDuring runs your delegate and hands back the count:
FirstChanceExceptions.CountDuring(() => parser.Parse(validInput)).ShouldBe(0);
It counts every exception type and stays on the calling thread, so it is safe under parallel runs.
Docs and license
Full guide and source: https://github.com/brhinescot/FirstChanceGuard.Xunit
MIT licensed.
| 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
- xunit.v3.extensibility.core (>= 3.2.2)
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 |
|---|