Skip to content
Back to skills

Nunit

ASecurity

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 tar...

  • 485 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added May 27, 2026
developmentgobashawstestingrefactoringgitapidatabasedocumentation

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 4 files and shows the line behind each finding

Scanned September 21, 2026

npx -y skills add managedcode/dotnet-skills --skill nunit --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Nunit?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Nunit
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/managedcode-nunit/badge)](https://www.skillsdirectory.com/skills/managedcode-nunit)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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

Files in this skill

  • SKILL.md10.2 KB
  • manifest.json108 B
  • references/anti-patterns.md10.2 KB
  • references/patterns.md7.8 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.