Skip to main content

nunit

Write, run, or repair .NET tests that use NUnit. Use when a repo uses `NUnit`, `[Test]`, `[TestCase]`, `[TestFixture]`, or NUnit3TestAdapter for VSTest or Microsoft.Testing.Platform execution. USE FOR: writing or reviewing NUnit tests; using [Test], [TestCase], [TestFixture], [SetUp], [TearDown] attributes; configuring NUnit3TestAdapter or NUnit.Analyzers. DO NOT USE FOR: unrelated stacks; generic tasks that do not need this specific guidance. INVOKES: inspect the repository context, edit targeted files, and run relevant build, test, lint, or validation commands when changes are made.

Source facts

Repository
managedcode/dotnet-skills
Last source activity
September 14, 2026 at 15:10
Detected SKILL.md language
English
Stars
499
Forks
40

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
4 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
nunit
description
Write, run, or repair .NET tests that use NUnit. Use when a repo uses `NUnit`, `[Test]`, `[TestCase]`, `[TestFixture]`, or NUnit3TestAdapter for VSTest or Microsoft.Testing.Platform execution. USE FOR: writing or reviewing NUnit tests; using [Test], [TestCase], [TestFixture], [SetUp], [TearDown] attributes; configuring NUnit3TestAdapter or NUnit.Analyzers. DO NOT USE FOR: unrelated stacks; generic tasks that do not need this specific guidance. INVOKES: inspect the repository context, edit targeted files, and run relevant build, test, lint, or validation commands when changes are made.
# NUnit Testing ## Diagnostic Output Budget Keep native test progress and ANSI visible; use the detected runner's supported flags (`--progress on --ansi on` for MTP/TUnit), not MTP switches on VSTest. Keep console logs at `Warning` or higher and one concise final summary. Do not replay progress redraws, successful-test output, or Information/Debug/Trace logs into model context. On failure/crash, show only the failing test/resource, root error, and relevant stack frames; deduplicate and cap each diagnostic response at 80 lines / 8 KiB. Never dump entire console/host/browser logs, HTML, TRX, or crash artifacts. Keep necessary artifacts size-bounded outside context, link them, and inspect exact bounded excerpts. Preserve the runner exit code through capture/filtering; disclose truncation. Silence alone does not establish a hang. ## Trigger On - writing or reviewing NUnit tests - using `[Test]`, `[TestCase]`, `[TestFixture]`, `[SetUp]`, `[TearDown]` attributes - configuring NUnit3TestAdapter or NUnit.Analyzers - migrating between NUnit versions - integrating NUnit with CI pipelines ## Documentation - [NUnit Documentation](https://docs.nunit.org/) - [NUnit GitHub](https://github.com/nunit/nunit) - [NUnit3TestAdapter](https://github.com/nunit/nunit3-vs-adapter) - [NUnit Analyzers](https://github.com/nunit/nunit.analyzers) ## Workflow 1. Detect whether the project uses NUnit 3.x or 4.x and which runner path is active: VSTest, Microsoft.Testing.Platform, IDE runner, or CI wrapper. 2. Keep test fixtures small, prefer focused assertions with `Assert.That`, and use `TestCase` or `TestCaseSource` only when parameterization improves signal. 3. Add `NUnit3TestAdapter`, `Microsoft.NET.Test.Sdk`, and `NUnit.Analyzers` when CLI discovery or analyzer coverage is missing. 4. Validate with the repo's real test command before changing assertion style or lifecycle hooks. ### References - [patterns.md](references/patterns.md) — Test patterns, assertions, parameterized tests, lifecycle - [anti-patterns.md](references/anti-patterns.md) — Common NUnit mistakes and fixes ## Package Selection | Package | Purpose | |---------|---------| | `NUnit` | Core testing framework | | `NUnit3TestAdapter` | VSTest adapter for `dotnet test` | | `NUnit.Analyzers` | Roslyn analyzers for NUnit best practices | | `Microsoft.NET.Test.Sdk` | Required for test discovery | ## Project Setup ```xml <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <IsPackable>false</IsPackable> <IsTestProject>true</IsTestProject> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.*" /> <PackageReference Include="NUnit" Version="4.*" /> <PackageReference Include="NUnit3TestAdapter" Version="4.*" /> <PackageReference Include="NUnit.Analyzers" Version="4.*"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference> </ItemGroup> </Project> ``` ## Test Patterns ### Basic Test Structure ```csharp [TestFixture] public class CalculatorTests { private Calculator _calculator; [SetUp] public void SetUp() { _calculator = new Calculator(); } [TearDown] public void TearDown() { _calculator?.Dispose(); } [Test] public void Add_TwoPositiveNumbers_ReturnsSum() { var result = _calculator.Add(2, 3); Assert.That(result, Is.EqualTo(5)); } [Test] public void Divide_ByZero_ThrowsException() { Assert.Throws<DivideByZeroException>(() => _calculator.Divide(10, 0)); } } ``` ### Parameterized Tests with TestCase ```csharp [TestFixture] public class ValidationTests { [TestCase("", false)] [TestCase("a", false)] [TestCase("ab", false)] [TestCase("abc", true)] [TestCase("valid@email.com", true)] public void IsValid_VariousInputs_ReturnsExpected(string input, bool expected) { var result = Validator.IsValid(input); Assert.That(result, Is.EqualTo(expected)); } [TestCase(1, 2, ExpectedResult = 3)] [TestCase(-1, 1, ExpectedResult = 0)] [TestCase(100, 200, ExpectedResult = 300)] public int Add_TestCases_ReturnsExpectedResult(int a, int b) { return _calculator.Add(a, b); } } ``` ### TestCaseSource for Complex Data ```csharp [TestFixture] public class OrderTests { private static IEnumerable<TestCaseData> OrderTestCases() { yield return new TestCaseData( new Order { Items = new[] { new Item { Price = 10 }, new Item { Price = 20 } } }, 30m ).SetName("TwoItems_CalculatesTotal"); yield return new TestCaseData( new Order { Items = Array.Empty<Item>() }, 0m ).SetName("EmptyOrder_ReturnsZero"); yield return new TestCaseData( new Order { Items = new[] { new Item { Price = 100 } }, DiscountPercent = 10 }, 90m ).SetName("WithDiscount_AppliesDiscount"); } [TestCaseSource(nameof(OrderTestCases))] public void CalculateTotal_VariousOrders_ReturnsExpected(Order order, decimal expected) { var result = order.CalculateTotal(); Assert.That(result, Is.EqualTo(expected)); } } ``` ### Constraint-Based Assertions ```csharp [Test] public void AssertionExamples() { // Equality Assert.That(actual, Is.EqualTo(expected)); Assert.That(actual, Is.Not.EqualTo(other)); // Comparison Assert.That(value, Is.GreaterThan(5)); Assert.That(value, Is.LessThanOrEqualTo(10)); Assert.That(value, Is.InRange(1, 100)); // String Assert.That(str, Does.StartWith("Hello")); Assert.That(str, Does.Contain("world")); Assert.That(str, Does.Match(@"\d{3}-\d{4}")); Assert.That(str, Is.EqualTo("HELLO").IgnoreCase); // Collection Assert.That(list, Has.Count.EqualTo(5)); Assert.That(list, Contains.Item("expected")); Assert.That(list, Is.All.GreaterThan(0)); Assert.That(list, Is.Unique); Assert.That(list, Is.Ordered); Assert.That(list, Has.Exactly(3).Items.GreaterThan(10)); // Type Assert.That(obj, Is.InstanceOf<MyClass>()); Assert.That(obj, Is.AssignableTo<IMyInterface>()); // Null Assert.That(obj, Is.Null); Assert.That(obj, Is.Not.Null); // Boolean Assert.That(condition, Is.True); Assert.That(condition, Is.False); // Exception Assert.That(() => DoSomething(), Throws.TypeOf<InvalidOperationException>()); Assert.That(() => DoSomething(), Throws.Exception.With.Message.Contains("error")); // Async Assert.That(async () => await DoAsync(), Throws.Nothing); } ``` ### Async Test Support ```csharp [TestFixture] public class AsyncServiceTests { private IAsyncService _service; [SetUp] public void SetUp() { _service = new AsyncService(); } [Test] public async Task GetDataAsync_ValidId_ReturnsData() { var result = await _service.GetDataAsync(1); Assert.That(result, Is.Not.Null); Assert.That(result.Id, Is.EqualTo(1)); } [Test] public void GetDataAsync_InvalidId_ThrowsException() { Assert.ThrowsAsync<NotFoundException>( async () => await _service.GetDataAsync(-1)); } [Test] public async Task ProcessAsync_CancellationRequested_ThrowsOperationCanceled() { using var cts = new CancellationTokenSource(); cts.Cancel(); Assert.ThrowsAsync<OperationCanceledException>( async () => await _service.ProcessAsync(cts.Token)); } } ``` ### OneTimeSetUp and OneTimeTearDown ```csharp [TestFixture] public class IntegrationTests { private static TestServer _server; private HttpClient _client; [OneTimeSetUp] public void OneTimeSetUp() { // Runs once before all tests in fixture _server = new TestServer(new WebHostBuilder().UseStartup<Startup>()); } [OneTimeTearDown] public void OneTimeTearDown() { // Runs once after all tests in fixture _server?.Dispose(); } [SetUp] public void SetUp() { // Runs before each test _client = _server.CreateClient(); } [TearDown] public void TearDown() { // Runs after each test _client?.Dispose(); } [Test] public async Task GetEndpoint_ReturnsSuccess() { var response = await _client.GetAsync("/api/data"); Assert.That(response.IsSuccessStatusCode, Is.True); } } ``` ## Categories and Filtering ```csharp [TestFixture] [Category("Integration")] public class DatabaseTests { [Test] [Category("Slow")] public void SlowDatabaseTest() { } [Test] [Category("Fast")] public void FastDatabaseTest() { } } // Run specific categories: // dotnet test --filter "Category=Fast" // dotnet test --filter "Category!=Slow" ``` ## Anti-Patterns to Avoid | Anti-Pattern | Why It's Bad | Better Approach | |--------------|--------------|-----------------| | Multiple asserts without clear purpose | Hard to identify which assertion failed | One logical assertion per test or use `Assert.Multiple` | | Test interdependence | Tests fail unpredictably | Each test should be independent | | Hardcoded test data | Brittle tests | Use `TestCase` or `TestCaseSource` | | Testing implementation details | Breaks on refactoring | Test behavior, not internals | | Missing `[SetUp]`/`[TearDown]` cleanup | Resource leaks | Always clean up resources | | Classic Assert syntax | Less readable | Use constraint model (`Assert.That`) | ## Running Tests ```bash # Run all tests dotnet test # VSTest: concise console results; MTP uses its own progress/ANSI flags dotnet test --logger "console;verbosity=minimal" # Filter by name dotnet test --filter "FullyQualifiedName~CalculatorTests" # Filter by category dotnet test --filter "Category=Unit" # Run in parallel dotnet test -- NUnit.NumberOfTestWorkers=4 ``` ## Deliver - NUnit tests following the Arrange-Act-Assert pattern - Parameterized tests with `[TestCase]` and `[TestCaseSource]` - Constraint-based assertions with `Assert.That` - Proper test lifecycle management ## Validate - Tests are independent and isolated - No hardcoded test data where parameterization is appropriate - Async tests use `async Task` not `async void` - Resources are properly disposed in `[TearDown]` or `[OneTimeTearDown]` - NUnit.Analyzers enabled to catch common issues
View on GitHub