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-43description: 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<!-- Generated from harness/github-copilot/plugins/csharp-dotnet-development/skills/csharp-nunit/SKILL.md by harness/claude-code/scripts/convert_from_copilot.py. Edit the source, not this file. -->78# C# NUnit testing910Write 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.1112## When to invoke1314- "Show NUnit best practices for this class."15- "Write data-driven NUnit tests."16- "Convert this test to `[TestCaseSource]`."17- "How should I organize a .NET test project?"18- "Fix these NUnit assertions."1920## Project setup and organization2122| Concern | Rule |23| --- | --- |24| Test project | Use a separate project named `[ProjectName].Tests`. |25| Packages | Reference `Microsoft.NET.Test.Sdk`, `NUnit`, and `NUnit3TestAdapter`. |26| Test command | Run tests with `dotnet test`. |27| Class naming | Match production classes, such as `CalculatorTests` for `Calculator`. |28| Test naming | Use `MethodName_Scenario_ExpectedBehavior`. |29| Grouping | Group tests by feature or component; use `[Category("CategoryName")]` for suites. |3031## Test structure and lifecycle3233| NUnit feature | Use for | Caution |34| --- | --- | --- |35| `[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. |36| `[Test]` | A single focused test method. | Do not test multiple behaviors in one method. |37| `[SetUp]` / `[TearDown]` | Per-test setup and cleanup. | Avoid hidden assertions or expensive shared state; this is the per-test hook. |38| `[OneTimeSetUp]` / `[OneTimeTearDown]` | Per-class expensive setup and cleanup. | Do not mutate shared state across tests unless reset; this is the per-class hook. |39| `[SetUpFixture]` | Assembly-level setup and teardown. | Use sparingly; assembly-level fixtures can hide global coupling. |40| `[Order]` | Rarely, when an external workflow truly requires order. | Prefer independent tests that can run in any order. |41| `[Explicit]` | Tests that should not run automatically. | Explain why manual execution is required. |42| `[Ignore("Reason")]` | Temporary skip. | Include a reason and remove when fixed. |43| `[Author("DeveloperName")]` and `[Description]` | Metadata required by team conventions. | Do not use metadata instead of readable test names. |4445## Data-driven tests4647| Attribute | Use when | Example shape |48| --- | --- | --- |49| `[TestCase]` | Inline values are short and obvious. | `[TestCase(1, 2, 3)]` |50| `[TestCaseSource]` | Cases need objects, generated values, or descriptive names. | Static method/property returning cases. |51| `[Values]` | Simple value lists for one parameter. | Combine with `[Combinatorial]` or `[Pairwise]`. |52| `[ValueSource]` | Values come from a property, field, or method-based source. | Keep source deterministic. |53| `[Random]` | Numeric fuzzing where nondeterminism is acceptable. | Record failures and prefer bounded ranges. |54| `[Range]` | Sequential numeric inputs. | Use small ranges to keep tests fast. |55| `[Combinatorial]` | All parameter combinations matter. | Watch explosion in case count. |56| `[Pairwise]` | Broad interaction coverage with fewer cases. | Use when exhaustive combinations are unnecessary. |5758## Assertions and isolation5960| Need | Preferred NUnit pattern |61| --- | --- |62| General assertions | `Assert.That(actual, Is.EqualTo(expected))` using the constraint model. |63| Value equality | Use `Is.EqualTo` inside `Assert.That(actual, Is.EqualTo(expected))`. |64| Reference equality | Use `Is.SameAs` inside `Assert.That(actual, Is.SameAs(expected))`. |65| Collection contains | Use `Contains.Item` inside `Assert.That(items, Contains.Item(expected))` or `CollectionAssert` when project style uses it. |66| String-specific checks | `StringAssert` or equivalent constraints; keep string-specific assertions readable. |67| Exceptions | `Assert.Throws<T>` for sync code and `Assert.ThrowsAsync<T>` for async code. |68| Failure clarity | Add descriptive assertion messages only when the expression is not self-explanatory. |69| Mocking | Use Moq or NSubstitute through interfaces to isolate dependencies. |70| Complex setup | Prefer small factories or a DI container only when it makes dependencies clearer. |7172## Gotchas7374- **Do not hide behavior in `[SetUp]`**: setup should arrange shared fixtures, not perform the act being tested.75- **Do not overuse `[Order]`**: ordered tests usually indicate state leakage.76- **Do not use random data without bounds**: `[Random]` can make failures hard to reproduce if ranges and assumptions are unclear.77- **Do not assert too much**: each test should prove one behavior with the minimum assertions needed.7879## Output template8081```markdown82## NUnit test result8384**Status:** ready | needs changes | blocked85**Target:** <class, method, or behavior>8687| Test | Pattern | Assertion | Notes |88| --- | --- | --- | --- |89| `MethodName_Scenario_ExpectedBehavior` | `[Test]` or `[TestCase]` | `Assert.That(...)` | <setup/mock/data source> |9091### Validation92- `dotnet test`: pass | fail | not run93```9495## Quality gate9697- [ ] Tests follow Arrange-Act-Assert and target one behavior each.98- [ ] Test names use `MethodName_Scenario_ExpectedBehavior` or the repository's established equivalent.99- [ ] Data-driven tests choose `[TestCase]`, `[TestCaseSource]`, `[Values]`, `[ValueSource]`, `[Random]`, `[Range]`, `[Combinatorial]`, or `[Pairwise]` intentionally.100- [ ] Assertions use `Assert.That` with constraints unless project style requires classic assertions such as `Assert.AreEqual`.101- [ ] Exception tests use `Assert.Throws<T>` or `Assert.ThrowsAsync<T>`.102- [ ] Tests are independent, idempotent, and runnable with `dotnet test`.