.NET unit testing (C# / F# / VB.NET) with xUnit.net as the primary framework - `[Fact]` single tests, `[Theory]` + `[InlineData]`/`[ClassData]`/`[MemberData]` parametrization, class and collection fixtures (`IClassFixture` / `ICollectionFixture`), parallel-execution config, `ITestOutputHelper` output, skip/traits filtering, and `dotnet test` CI with trx + coverage. Includes framework choice (xUnit for new projects; match an existing NUnit/MSTest convention detected from csproj PackageReferences; legacy .NET Framework 4.x → NUnit or MSTest) and test-authoring conventions (AAA mapping, argument-order traps, no fabricated methods, no smoke asserts). References cover NUnit (`[TestCase]`, constraint-model `Assert.That`), MSTest (`[TestClass]` / `[DataRow]` / TestContext), and the FluentAssertions `.Should()` catalog including the v8 commercial-license change. Use for any .NET unit-test task: choosing or configuring a framework, writing or parameterizing tests, fixtures, or wiring CI.
75
94%
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
Per xunit.net:
xUnit.net is the current .NET test standard (used by .NET Foundation
projects and Microsoft's own .NET runtime). v3 released 2024; v2 still
widely used in production. This skill covers xUnit as the default, with
NUnit and MSTest as references for existing conventions. Lifecycle scope
(configure / run / parameterize / fixtures / CI); test code hygiene is in
test-code-conventions (qa-test-review).
.csproj for <PackageReference Include="...">: xunit / xunit.v3 →
xUnit; NUnit / NUnit3TestAdapter → NUnit; MSTest /
MSTest.TestFramework → MSTest. If exactly one is present, match it -
switching frameworks mid-solution forces a wholesale assertion rewrite
for no quality gain.dotnet new xunit is a first-party template
(learn.microsoft.com/dotnet/core/testing).FluentAssertions already in deps → retain it regardless of
framework (it auto-detects xUnit, NUnit, and MSTest) →
references/fluentassertions.md -
including the v8 commercial-license change.dotnet new xunit -n MyProjectTests
# Or in existing project:
dotnet add package xunit
dotnet add package xunit.runner.visualstudio
dotnet add package Microsoft.NET.Test.Sdkusing Xunit;
public class CalculatorTests
{
[Fact]
public void Adds_TwoNumbers()
{
Assert.Equal(3, Calculator.Add(1, 2));
}
}Run: dotnet test. Verify the run reports Passed! with the expected
test count; if it discovers 0 tests, confirm the class is public, the
method carries [Fact], and all three packages are installed.
Per xn-docs:
[Theory]
[InlineData(1, 2, 3)]
[InlineData(0, 0, 0)]
[InlineData(-1, 1, 0)]
public void Adds_VariousInputs(int a, int b, int expected)
{
Assert.Equal(expected, Calculator.Add(a, b));
}
// Method-based data source
public static IEnumerable<object[]> AddCases =>
new List<object[]> {
new object[] { 1, 2, 3 },
new object[] { 0, 0, 0 },
};
[Theory]
[MemberData(nameof(AddCases))]
public void Adds_FromMemberData(int a, int b, int expected) { ... }[ClassData(typeof(AddTestData))] covers class-based sources
(IEnumerable<object[]> implementations). A [Theory] without a data
attribute never runs.
[Fact(Skip = "Requires staging DB")]
public void SkippedTest() { }
[Fact]
[Trait("Category", "Integration")]
public void IntegrationTest() { }
// Filter: dotnet test --filter "Category=Integration"xUnit's lifecycle: constructor as setup, IDisposable.Dispose as
teardown - a new test-class instance per test. Shared setup scales up
through fixtures:
// Class fixture: shared across all tests in one class
public class DatabaseFixture : IDisposable {
public DbConnection Connection { get; }
public DatabaseFixture() { Connection = OpenConnection(); }
public void Dispose() { Connection.Close(); }
}
public class UserTests : IClassFixture<DatabaseFixture> {
private readonly DatabaseFixture _fixture;
public UserTests(DatabaseFixture fixture) { _fixture = fixture; }
}
// Collection fixture: shared across multiple test classes
[CollectionDefinition("DbCollection")]
public class DbCollection : ICollectionFixture<DatabaseFixture> { }
[Collection("DbCollection")]
public class TestsA { ... }
[Collection("DbCollection")]
public class TestsB { ... } // shares the same DatabaseFixtureBy default xUnit runs collections in parallel; tests in the same collection run sequentially. Assembly-level tuning:
[assembly: CollectionBehavior(DisableTestParallelization = true)]
// or
[assembly: CollectionBehavior(MaxParallelThreads = 4)]Pattern: DB-backed classes share one fixture via [Collection] (runs
sequentially against the shared connection) while pure-logic tests
parallelize freely.
xUnit suppresses Console.WriteLine in tests:
public class TestsWithOutput {
private readonly ITestOutputHelper _output;
public TestsWithOutput(ITestOutputHelper output) { _output = output; }
[Fact]
public void LogsContext() {
_output.WriteLine("Test running at {0}", DateTime.UtcNow);
}
}- run: dotnet test --logger "trx;LogFileName=test-results.trx" \
--collect:"XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover
- uses: codecov/codecov-action@v4
with: { files: ./coverage/coverage.opencover.xml }The same dotnet test --logger trx --collect shape works for NUnit and
MSTest projects.
When authoring a new unit test in an existing project:
.csproj (the
PackageReference table in Choosing). Multiple framework signals in one
solution → stop and ask which to use.<TestProject>/Tests/<ClassUnderTest>Tests.cs; never modify existing
test files.Assert.True(true),
result.Should().NotBeNull() when a concrete value is named).(expected, actual);
NUnit's constraint model (Assert.That(actual, Is.EqualTo(expected)))
and FluentAssertions (actual.Should().Be(expected)) sidestep the trap.Faker<T>
builders via synthetic-data-toolkit (qa-test-data); never install
packages as a side effect.| Anti-pattern | Why it fails | Fix |
|---|---|---|
Console.WriteLine instead of ITestOutputHelper | Output suppressed | Step 6 |
[Theory] without a data attribute | Test never runs | Always include [InlineData] etc. (Step 3) |
Shared mutable state in IClassFixture | Test-order dependence | Per-test fresh state or [Collection] synchronization (Step 5) |
| Static fields for cross-test state | xUnit creates a new instance per test; statics leak | Constructor per test; fixtures for shared setup |
Conflating xUnit constructor-per-test with NUnit [OneTimeSetUp] | Constructor runs every test | IClassFixture<T> for per-fixture setup |
| Skip parallel tuning at scale | Slow CI | Assembly + collection config (Step 5) |
--filter.test-code-conventions (qa-test-review) - test code hygiene