Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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 target
.claude/skills/managedcode-nunit/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-03 | ✗→✓ | ▲ Improved | 96% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 128% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 325% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 230% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 397% | 0% |
[Test], [TestCase], [TestFixture], [SetUp], [TearDown] attributesAssert.That, and use TestCase or TestCaseSource only when parameterization improves signal.NUnit3TestAdapter, Microsoft.NET.Test.Sdk, and NUnit.Analyzers when CLI discovery or analyzer coverage is missing.| 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 |
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>
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)); } }
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); } }
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)); } }
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); }
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)); } }
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); } }
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-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) |
bash# Run all tests dotnet test # Run with verbosity dotnet test --logger "console;verbosity=detailed" # Filter by name dotnet test --filter "FullyQualifiedName~CalculatorTests" # Filter by category dotnet test --filter "Category=Unit" # Run in parallel dotnet test -- NUnit.NumberOfTestWorkers=4
[TestCase] and [TestCaseSource]Assert.Thatasync Task not async void[TearDown] or [OneTimeTearDown]Other measured skills in the registry, with their headline benchmark lift.