.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
Companion reference for dotnet-unit-tests. Consult for existing NUnit
codebases, teams preferring constraint-model assertions, or legacy .NET
Framework 4.x targets. For brand-new .NET code, xUnit (SKILL.md) is the
more mainstream default.
Per docs.nunit.org:
NUnit's distinguishing properties vs xUnit: annotation-driven lifecycle,
constraint-model assertions (Assert.That(actual, Is.EqualTo(expected))),
and multiple parametrize attributes ([TestCase], [Values], [Random],
[Range]).
dotnet new nunit -n MyTests
# Or: dotnet add package NUnit + NUnit3TestAdapter + Microsoft.NET.Test.Sdkusing NUnit.Framework;
[TestFixture]
public class CalculatorTests
{
[Test]
public void Adds_TwoNumbers()
{
Assert.That(Calculator.Add(1, 2), Is.EqualTo(3));
}
}[TestFixture] is optional in NUnit 3+ (classes with [Test] methods are
auto-discovered) - pick a convention and document it. Run: dotnet test.
[Test]
[TestCase(1, 2, 3)]
[TestCase(0, 0, 0)]
[TestCase(-1, 1, 0)]
public void Adds_VariousInputs(int a, int b, int expected)
{
Assert.That(Calculator.Add(a, b), Is.EqualTo(expected));
}
// Combinatorial: 3 × 2 = 6 runs
[Test]
public void Adds_FromValues([Values(1, 2, 3)] int a, [Values(0, 1)] int b)
{
Assert.That(Calculator.Add(a, b), Is.EqualTo(a + b));
}
[Test]
public void Adds_Range([Range(0, 10, 2)] int n) // n = 0, 2, 4, 6, 8, 10
{
Assert.That(Calculator.Add(n, n), Is.EqualTo(n * 2));
}
// Method-source
[Test]
[TestCaseSource(nameof(AddCases))]
public void Adds_FromSource(int a, int b, int expected) { ... }
public static IEnumerable<TestCaseData> AddCases()
{
yield return new TestCaseData(1, 2, 3);
yield return new TestCaseData(0, 0, 0);
}[Random(0, 100, 5)] generates random values - pin a seed
([Random(seed: 42, ...)]) for CI reproducibility.
Per nu-docs - the constraint model composes
(Is.Not.Null.And.Not.Empty) and produces detailed failure messages;
classic Assert.AreEqual / Assert.IsTrue still work but are discouraged
in NUnit 3+:
Assert.That(value, Is.EqualTo(expected));
Assert.That(value, Is.Not.EqualTo(expected));
Assert.That(value, Is.GreaterThan(0));
Assert.That(s, Does.Contain("substring"));
Assert.That(s, Does.Match("regex"));
Assert.That(list, Has.Count.EqualTo(3));
Assert.That(list, Has.Member("alice"));
Assert.That(list, Is.Ordered);
Assert.That(list, Has.All.GreaterThan(0));
Assert.That(opt, Is.Null);
Assert.That(value, Is.InstanceOf<MyClass>());
Assert.That(action, Throws.TypeOf<ArgumentException>());
Assert.That(actual, Is.EqualTo(0.0).Within(0.001)); // float tolerance[OneTimeSetUp] / [OneTimeTearDown] (once per fixture) and [SetUp] /
[TearDown] (per test).
[Test]
[Category("Integration")]
public void IntegrationTest() { }
// Filter: dotnet test --filter Category=Integration
[TestFixture("postgres")]
[TestFixture("mysql")]
public class DatabaseTests
{
private string _engine;
public DatabaseTests(string engine) { _engine = engine; }
[Test]
public void Connect() { /* runs against postgres AND mysql */ }
}Same shape as xUnit:
dotnet test --logger "trx;LogFileName=test-results.trx" --collect:"XPlat Code Coverage".
| Anti-pattern | Why it fails | Fix |
|---|---|---|
Classic Assert.AreEqual style | Discouraged in NUnit 3+ | Constraint model Assert.That(...) |
Unseeded [Random] | Non-deterministic CI runs | [Random(seed: 42, ...)] |
| Mix NUnit + xUnit in one solution | Two runners | Pick one |
Assert.Equal() simplicity.