dotnet-tunit-test
Guidelines for writing TUnit tests in .NET, including setup, assertions, async testing, and best practices. Use when writing unit tests with TUnit framework, setting up TUnit in a .NET project, or migrating from other test frameworks to TUnit.
What this skill does
# Testing with TUnit
## When to Use This Skill
Use this skill when:
- Creating a new TUnit test project or adding TUnit to an existing solution
- Writing unit, integration, or acceptance tests using TUnit
- Migrating tests from xUnit, NUnit, or MSTest to TUnit
- Configuring data-driven tests with `[Arguments]`, `[MethodDataSource]`, or `[ClassDataSource]`
- Setting up test lifecycle hooks (`[Before]`/`[After]`)
- Controlling parallelism with `[NotInParallel]`, `[DependsOn]`, or parallel groups
- Writing ASP.NET Core integration tests with `TUnit.AspNetCore`
- Configuring TUnit for CI/CD pipelines with coverage and TRX reports
---
## What is TUnit?
TUnit is a modern, source-generated testing framework for .NET built on the Microsoft Testing Platform. Key characteristics:
- **Source generated** - Tests are discovered at compile time, not via reflection
- **Parallel by default** - Tests run concurrently for speed
- **Async-first assertions** - All assertions must be awaited
- **New class instance per test** - Test classes are instantiated fresh for each test method
- **No `[TestClass]` attribute needed** - Only `[Test]` on methods
- **Native AOT and single-file support** - Works where reflection-based frameworks cannot
- **Built-in code coverage and TRX reports** - No need for Coverlet
---
## Installation
### From Template (Recommended)
```bash
dotnet new install TUnit.Templates
dotnet new TUnit -n "MyApp.Tests"
```
### Manual Setup
```bash
dotnet new console --name MyApp.Tests
cd MyApp.Tests
dotnet add package TUnit --prerelease
```
Remove any auto-generated `Program.cs` -- TUnit handles the entry point.
### Project File
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="TUnit" Version="*" />
</ItemGroup>
</Project>
```
### CRITICAL: Do NOT Use These Packages
| Package | Why |
|---------|-----|
| `Microsoft.NET.Test.Sdk` | Breaks TUnit test discovery -- TUnit uses Microsoft.Testing.Platform, not VSTest |
| `coverlet.collector` / `coverlet.msbuild` | Incompatible with TUnit -- use the built-in `--coverage` flag instead |
### Global Usings
TUnit automatically provides global usings for `TUnit.Core`, `TUnit.Assertions`, and `TUnit.Assertions.Extensions`. You do not need explicit `using` statements in test files.
---
## Writing Tests
### Basic Test
```csharp
namespace MyApp.Tests;
public class CalculatorTests
{
[Test]
public async Task Add_TwoNumbers_ReturnsSum()
{
var result = 2 + 3;
await Assert.That(result).IsEqualTo(5);
}
}
```
### Test Method Signatures
```csharp
[Test]
public void SyncTest() // Valid -- synchronous, no assertions
{
var result = Calculate(2, 3);
}
[Test]
public async Task AsyncTest() // Recommended -- required if using assertions
{
await Assert.That(42).IsEqualTo(42);
}
// async void is NOT allowed -- compiler error
```
**Rule**: If you use `Assert.That(...)`, the test method **must** be `async Task` because assertions are awaitable.
---
## Assertions
All TUnit assertions follow the pattern `await Assert.That(actual).SomeCondition()`.
### Core Assertions
```csharp
// Equality
await Assert.That(result).IsEqualTo(5);
await Assert.That(result).IsNotEqualTo(0);
// Comparison
await Assert.That(score).IsGreaterThan(70);
await Assert.That(age).IsLessThanOrEqualTo(100);
await Assert.That(temp).IsBetween(20, 30);
// Boolean
await Assert.That(isValid).IsTrue();
await Assert.That(isDeleted).IsFalse();
// Null
await Assert.That(result).IsNotNull();
await Assert.That(optional).IsNull();
// Type
await Assert.That(obj).IsTypeOf<MyClass>();
```
### String Assertions
```csharp
await Assert.That(message).Contains("Hello");
await Assert.That(filename).StartsWith("test_");
await Assert.That(email).Matches(@"^[\w\.-]+@[\w\.-]+\.\w+$");
await Assert.That(input).IsNotEmpty();
```
### Collection Assertions
```csharp
await Assert.That(numbers).Contains(42);
await Assert.That(items).Count().IsEqualTo(5);
await Assert.That(list).IsNotEmpty();
await Assert.That(values).All(x => x > 0);
await Assert.That(numbers).IsEquivalentTo(new[] { 5, 4, 3, 2, 1 }); // order-independent
await Assert.That(numbers).IsInOrder();
```
### Exception Assertions
```csharp
// Basic exception testing
await Assert.That(() => int.Parse("not a number"))
.Throws<FormatException>();
// Async exception testing
await Assert.That(async () => await FailingOperationAsync())
.Throws<HttpRequestException>();
// Exact type (no subclasses)
await Assert.That(() => throw new ArgumentNullException())
.ThrowsExactly<ArgumentNullException>();
// Exception message
await Assert.That(() => throw new InvalidOperationException("Operation failed"))
.Throws<InvalidOperationException>()
.WithMessage("Operation failed");
await Assert.That(() => throw new ArgumentException("The parameter 'userId' is invalid"))
.Throws<ArgumentException>()
.WithMessageContaining("userId");
// ArgumentException parameter name
await Assert.That(() => ValidateUser(null!))
.Throws<ArgumentNullException>()
.WithParameterName("user");
// Inner exceptions
await Assert.That(() => ThrowWithInner())
.Throws<InvalidOperationException>()
.WithInnerException()
.Throws<FormatException>();
// No exception thrown
await Assert.That(() => int.Parse("42"))
.ThrowsNothing();
```
### Chaining with And / Or
```csharp
await Assert.That(username)
.IsNotNull()
.And.IsNotEmpty()
.And.Length().IsGreaterThan(3)
.And.Length().IsLessThan(20);
await Assert.That(statusCode)
.IsEqualTo(200)
.Or.IsEqualTo(201)
.Or.IsEqualTo(204);
```
### Assert.Multiple (Report All Failures)
```csharp
using (Assert.Multiple())
{
await Assert.That(user.FirstName).IsEqualTo("John");
await Assert.That(user.LastName).IsEqualTo("Doe");
await Assert.That(user.Age).IsGreaterThan(18);
}
// All failures reported together, not just the first one
```
### Floating-Point Tolerance
```csharp
await Assert.That(3.14159).IsEqualTo(Math.PI).Within(0.001);
```
### CRITICAL: Always Await Assertions
```csharp
// WRONG -- assertion never executes, test always passes
Assert.That(result).IsEqualTo(5);
// CORRECT
await Assert.That(result).IsEqualTo(5);
```
TUnit includes a built-in analyzer that warns about unawaited assertions.
---
## Data-Driven Tests
### [Arguments] -- Compile-Time Constants
```csharp
[Test]
[Arguments(1, 1, 2)]
[Arguments(1, 2, 3)]
[Arguments(2, 2, 4)]
public async Task Add_ReturnsExpectedResult(int a, int b, int expected)
{
await Assert.That(a + b).IsEqualTo(expected);
}
```
Supports metadata: `DisplayName`, `Categories`, `Skip`:
```csharp
[Test]
[Arguments("Chrome", "120")]
[Arguments("Safari", "17", Skip = "Safari not available in CI")]
public async Task BrowserTest(string browser, string version) { }
```
### [MethodDataSource] -- Dynamic/Complex Data
```csharp
public static class TestData
{
public static IEnumerable<Func<(int A, int B, int Expected)>> AdditionCases()
{
yield return () => (1, 2, 3);
yield return () => (2, 2, 4);
yield return () => (5, 5, 10);
}
}
public class MathTests
{
[Test]
[MethodDataSource(typeof(TestData), nameof(TestData.AdditionCases))]
public async Task Add_WithData(int a, int b, int expected)
{
await Assert.That(a + b).IsEqualTo(expected);
}
}
```
For reference types, return `Func<T>` (not `T`) to ensure each test gets a fresh instance.
### [ClassDataSource] -- Injectable Shared Resources
```csharp
public class TestWebServer : IAsyncInitializer, IAsyncDisposable
{
public WebApplicationFactory<Program>? Factory { get; private set; }
public async Task InitializeAsync()
{
Factory = new WebApplicationRelated in Writing & Docs
jax-development
IncludedUse this skill when the user is writing, debugging, profiling, refactoring, reviewing, benchmarking, parallelising, exporting, or explaining JAX code, or when they mention JAX, jax.numpy, jit, grad, value_and_grad, vmap, scan, lax, random keys, pytrees, jax.Array, sharding, Mesh, PartitionSpec, NamedSharding, pmap, shard_map, Pallas, XLA, StableHLO, checkify, profiler, or the JAX repo. It helps turn NumPy or PyTorch-style code into pure functional JAX, fix tracer/control-flow/shape/PRNG bugs, remove recompiles and host-device syncs, choose transforms and sharding strategies, inspect jaxpr/lowering/IR, and benchmark compiled code correctly.
nature-article-writer
IncludedDrafts, rewrites, diagnostically critiques, and style-calibrates primary research manuscripts for Nature and Nature Portfolio journals. Use when the user wants a Nature-style title, summary paragraph or abstract, introduction, results, discussion, methods, figure legends, presubmission enquiry, cover letter, reviewer response, or when a scientific draft sounds generic, jargon-heavy, structurally weak, or AI-ish and needs precise, broad-reader-friendly prose without inventing data, analyses, or references. Best for primary research articles and letters rather than reviews or press releases unless explicitly adapting one.
deckrd
IncludedDocument-driven framework that derives requirements, specifications, implementation plans, and executable tasks from goals through structured AI dialogue. Use when user says "write requirements", "create spec", "plan implementation", "derive tasks", "structure this feature", "break down into tasks", or "document this module". Also use for reverse engineering existing code into docs (/deckrd rev). Do NOT use for direct code writing — use /deckrd-coder after tasks are generated. Do NOT use when the user only wants to run or fix existing code without planning.
clinical-decision-support
IncludedGenerate professional clinical decision support (CDS) documents for pharmaceutical and clinical research settings, including patient cohort analyses (biomarker-stratified with outcomes) and treatment recommendation reports (evidence-based guidelines with decision algorithms). Supports GRADE evidence grading, statistical analysis (hazard ratios, survival curves, waterfall plots), biomarker integration, and regulatory compliance. Outputs publication-ready LaTeX/PDF format optimized for drug development, clinical research, and evidence synthesis.
handling-sf-data
IncludedSalesforce data operations with 130-point scoring. Use this skill to create, update, delete, bulk import/export, generate test data, and clean up org records using sf CLI and anonymous Apex. TRIGGER when: user creates test data, performs bulk import/export, uses sf data CLI commands, needs data factory patterns for Apex tests, or needs to seed/clean records in a Salesforce org. DO NOT TRIGGER when: SOQL query writing only (use querying-soql), Apex test execution (use running-apex-tests), or metadata deployment (use deploying-metadata).
accelint-ac-to-playwright
IncludedConvert and validate acceptance criteria for Playwright test automation. Use when user asks to (1) review/evaluate/check if AC are ready for automation, (2) assess if AC can be converted as-is, (3) validate AC quality for Playwright, (4) turn AC into tests, (5) generate tests from acceptance criteria, (6) convert .md bullets or .feature Gherkin files to Playwright specs, (7) create test automation from requirements. Handles both bullet-style markdown and Gherkin syntax with JSON test plan generation and validation.