C# NUnit testing
Write focused NUnit tests that follow Arrange-Act-Assert, use the NUnit constraint model, isolate units under test, and keep data-driven cases readable and deterministic.
When to invoke
- "Show NUnit best practices for this class."
- "Write data-driven NUnit tests."
- "Convert this test to
[TestCaseSource]."
- "How should I organize a .NET test project?"
- "Fix these NUnit assertions."
Project setup and organization
| Concern |
Rule |
| Test project |
Use a separate project named [ProjectName].Tests. |
| Packages |
Reference Microsoft.NET.Test.Sdk, NUnit, and NUnit3TestAdapter. |
| Test command |
Run tests with dotnet test. |
| Class naming |
Match production classes, such as CalculatorTests for Calculator. |
| Test naming |
Use MethodName_Scenario_ExpectedBehavior. |
| Grouping |
Group tests by feature or component; use [Category("CategoryName")] for suites. |
Test structure and lifecycle
| NUnit feature |
Use for |
Caution |
[TestFixture] |
Marking a test class when explicit fixture metadata helps. |
Modern NUnit can discover many classes without it, but keep it when the project uses it consistently. |
[Test] |
A single focused test method. |
Do not test multiple behaviors in one method. |
[SetUp] / [TearDown] |
Per-test setup and cleanup. |
Avoid hidden assertions or expensive shared state; this is the per-test hook. |
[OneTimeSetUp] / [OneTimeTearDown] |
Per-class expensive setup and cleanup. |
Do not mutate shared state across tests unless reset; this is the per-class hook. |
[SetUpFixture] |
Assembly-level setup and teardown. |
Use sparingly; assembly-level fixtures can hide global coupling. |
[Order] |
Rarely, when an external workflow truly requires order. |
Prefer independent tests that can run in any order. |
[Explicit] |
Tests that should not run automatically. |
Explain why manual execution is required. |
[Ignore("Reason")] |
Temporary skip. |
Include a reason and remove when fixed. |
[Author("DeveloperName")] and [Description] |
Metadata required by team conventions. |
Do not use metadata instead of readable test names. |
Data-driven tests
| Attribute |
Use when |
Example shape |
[TestCase] |
Inline values are short and obvious. |
[TestCase(1, 2, 3)] |
[TestCaseSource] |
Cases need objects, generated values, or descriptive names. |
Static method/property returning cases. |
[Values] |
Simple value lists for one parameter. |
Combine with [Combinatorial] or [Pairwise]. |
[ValueSource] |
Values come from a property, field, or method-based source. |
Keep source deterministic. |
[Random] |
Numeric fuzzing where nondeterminism is acceptable. |
Record failures and prefer bounded ranges. |
[Range] |
Sequential numeric inputs. |
Use small ranges to keep tests fast. |
[Combinatorial] |
All parameter combinations matter. |
Watch explosion in case count. |
[Pairwise] |
Broad interaction coverage with fewer cases. |
Use when exhaustive combinations are unnecessary. |
Assertions and isolation
| Need |
Preferred NUnit pattern |
| General assertions |
Assert.That(actual, Is.EqualTo(expected)) using the constraint model. |
| Value equality |
Use Is.EqualTo inside Assert.That(actual, Is.EqualTo(expected)). |
| Reference equality |
Use Is.SameAs inside Assert.That(actual, Is.SameAs(expected)). |
| Collection contains |
Use Contains.Item inside Assert.That(items, Contains.Item(expected)) or CollectionAssert when project style uses it. |
| String-specific checks |
StringAssert or equivalent constraints; keep string-specific assertions readable. |
| Exceptions |
Assert.Throws<T> for sync code and Assert.ThrowsAsync<T> for async code. |
| Failure clarity |
Add descriptive assertion messages only when the expression is not self-explanatory. |
| Mocking |
Use Moq or NSubstitute through interfaces to isolate dependencies. |
| Complex setup |
Prefer small factories or a DI container only when it makes dependencies clearer. |
Gotchas
- Do not hide behavior in
[SetUp]: setup should arrange shared fixtures, not perform the act being tested.
- Do not overuse
[Order]: ordered tests usually indicate state leakage.
- Do not use random data without bounds:
[Random] can make failures hard to reproduce if ranges and assumptions are unclear.
- Do not assert too much: each test should prove one behavior with the minimum assertions needed.
Output template
## NUnit test result
**Status:** ready | needs changes | blocked
**Target:** <class, method, or behavior>
| Test | Pattern | Assertion | Notes |
| --- | --- | --- | --- |
| `MethodName_Scenario_ExpectedBehavior` | `[Test]` or `[TestCase]` | `Assert.That(...)` | <setup/mock/data source> |
### Validation
- `dotnet test`: pass | fail | not run
Quality gate
1---2name: csharp-nunit-23description: Design, write, and review NUnit tests for .NET projects, including standard tests, data-driven tests, assertions, setup/teardown, categories, and isolation with mocks. Use this skill when the user asks for NUnit best practices, `dotnet test`, `[TestCase]`, `[TestCaseSource]`, or C# unit test structure.4---56# C# NUnit testing78Write focused NUnit tests that follow Arrange-Act-Assert, use the NUnit constraint model, isolate units under test, and keep data-driven cases readable and deterministic.910## When to invoke1112- "Show NUnit best practices for this class."13- "Write data-driven NUnit tests."14- "Convert this test to `[TestCaseSource]`."15- "How should I organize a .NET test project?"16- "Fix these NUnit assertions."1718## Project setup and organization1920| Concern | Rule |21| --- | --- |22| Test project | Use a separate project named `[ProjectName].Tests`. |23| Packages | Reference `Microsoft.NET.Test.Sdk`, `NUnit`, and `NUnit3TestAdapter`. |24| Test command | Run tests with `dotnet test`. |25| Class naming | Match production classes, such as `CalculatorTests` for `Calculator`. |26| Test naming | Use `MethodName_Scenario_ExpectedBehavior`. |27| Grouping | Group tests by feature or component; use `[Category("CategoryName")]` for suites. |2829## Test structure and lifecycle3031| NUnit feature | Use for | Caution |32| --- | --- | --- |33| `[TestFixture]` | Marking a test class when explicit fixture metadata helps. | Modern NUnit can discover many classes without it, but keep it when the project uses it consistently. |34| `[Test]` | A single focused test method. | Do not test multiple behaviors in one method. |35| `[SetUp]` / `[TearDown]` | Per-test setup and cleanup. | Avoid hidden assertions or expensive shared state; this is the per-test hook. |36| `[OneTimeSetUp]` / `[OneTimeTearDown]` | Per-class expensive setup and cleanup. | Do not mutate shared state across tests unless reset; this is the per-class hook. |37| `[SetUpFixture]` | Assembly-level setup and teardown. | Use sparingly; assembly-level fixtures can hide global coupling. |38| `[Order]` | Rarely, when an external workflow truly requires order. | Prefer independent tests that can run in any order. |39| `[Explicit]` | Tests that should not run automatically. | Explain why manual execution is required. |40| `[Ignore("Reason")]` | Temporary skip. | Include a reason and remove when fixed. |41| `[Author("DeveloperName")]` and `[Description]` | Metadata required by team conventions. | Do not use metadata instead of readable test names. |4243## Data-driven tests4445| Attribute | Use when | Example shape |46| --- | --- | --- |47| `[TestCase]` | Inline values are short and obvious. | `[TestCase(1, 2, 3)]` |48| `[TestCaseSource]` | Cases need objects, generated values, or descriptive names. | Static method/property returning cases. |49| `[Values]` | Simple value lists for one parameter. | Combine with `[Combinatorial]` or `[Pairwise]`. |50| `[ValueSource]` | Values come from a property, field, or method-based source. | Keep source deterministic. |51| `[Random]` | Numeric fuzzing where nondeterminism is acceptable. | Record failures and prefer bounded ranges. |52| `[Range]` | Sequential numeric inputs. | Use small ranges to keep tests fast. |53| `[Combinatorial]` | All parameter combinations matter. | Watch explosion in case count. |54| `[Pairwise]` | Broad interaction coverage with fewer cases. | Use when exhaustive combinations are unnecessary. |5556## Assertions and isolation5758| Need | Preferred NUnit pattern |59| --- | --- |60| General assertions | `Assert.That(actual, Is.EqualTo(expected))` using the constraint model. |61| Value equality | Use `Is.EqualTo` inside `Assert.That(actual, Is.EqualTo(expected))`. |62| Reference equality | Use `Is.SameAs` inside `Assert.That(actual, Is.SameAs(expected))`. |63| Collection contains | Use `Contains.Item` inside `Assert.That(items, Contains.Item(expected))` or `CollectionAssert` when project style uses it. |64| String-specific checks | `StringAssert` or equivalent constraints; keep string-specific assertions readable. |65| Exceptions | `Assert.Throws<T>` for sync code and `Assert.ThrowsAsync<T>` for async code. |66| Failure clarity | Add descriptive assertion messages only when the expression is not self-explanatory. |67| Mocking | Use Moq or NSubstitute through interfaces to isolate dependencies. |68| Complex setup | Prefer small factories or a DI container only when it makes dependencies clearer. |6970## Gotchas7172- **Do not hide behavior in `[SetUp]`**: setup should arrange shared fixtures, not perform the act being tested.73- **Do not overuse `[Order]`**: ordered tests usually indicate state leakage.74- **Do not use random data without bounds**: `[Random]` can make failures hard to reproduce if ranges and assumptions are unclear.75- **Do not assert too much**: each test should prove one behavior with the minimum assertions needed.7677## Output template7879```markdown80## NUnit test result8182**Status:** ready | needs changes | blocked83**Target:** <class, method, or behavior>8485| Test | Pattern | Assertion | Notes |86| --- | --- | --- | --- |87| `MethodName_Scenario_ExpectedBehavior` | `[Test]` or `[TestCase]` | `Assert.That(...)` | <setup/mock/data source> |8889### Validation90- `dotnet test`: pass | fail | not run91```9293## Quality gate9495- [ ] Tests follow Arrange-Act-Assert and target one behavior each.96- [ ] Test names use `MethodName_Scenario_ExpectedBehavior` or the repository's established equivalent.97- [ ] Data-driven tests choose `[TestCase]`, `[TestCaseSource]`, `[Values]`, `[ValueSource]`, `[Random]`, `[Range]`, `[Combinatorial]`, or `[Pairwise]` intentionally.98- [ ] Assertions use `Assert.That` with constraints unless project style requires classic assertions such as `Assert.AreEqual`.99- [ ] Exception tests use `Assert.Throws<T>` or `Assert.ThrowsAsync<T>`.100- [ ] Tests are independent, idempotent, and runnable with `dotnet test`.