Get the FREE Ultimate OpenClaw Setup Guide →

csharp-tunit

Scanned
npx machina-cli add skill github/awesome-copilot/csharp-tunit --openclaw
Files (1)
SKILL.md
5.0 KB

TUnit Best Practices

Your goal is to help me write effective unit tests with TUnit, covering both standard and data-driven testing approaches.

Project Setup

  • Use a separate test project with naming convention [ProjectName].Tests
  • Reference TUnit package and TUnit.Assertions for fluent assertions
  • Create test classes that match the classes being tested (e.g., CalculatorTests for Calculator)
  • Use .NET SDK test commands: dotnet test for running tests
  • TUnit requires .NET 8.0 or higher

Test Structure

  • No test class attributes required (like xUnit/NUnit)
  • Use [Test] attribute for test methods (not [Fact] like xUnit)
  • Follow the Arrange-Act-Assert (AAA) pattern
  • Name tests using the pattern MethodName_Scenario_ExpectedBehavior
  • Use lifecycle hooks: [Before(Test)] for setup and [After(Test)] for teardown
  • Use [Before(Class)] and [After(Class)] for shared context between tests in a class
  • Use [Before(Assembly)] and [After(Assembly)] for shared context across test classes
  • TUnit supports advanced lifecycle hooks like [Before(TestSession)] and [After(TestSession)]

Standard Tests

  • Keep tests focused on a single behavior
  • Avoid testing multiple behaviors in one test method
  • Use TUnit's fluent assertion syntax with await Assert.That()
  • Include only the assertions needed to verify the test case
  • Make tests independent and idempotent (can run in any order)
  • Avoid test interdependencies (use [DependsOn] attribute if needed)

Data-Driven Tests

  • Use [Arguments] attribute for inline test data (equivalent to xUnit's [InlineData])
  • Use [MethodData] for method-based test data (equivalent to xUnit's [MemberData])
  • Use [ClassData] for class-based test data
  • Create custom data sources by implementing ITestDataSource
  • Use meaningful parameter names in data-driven tests
  • Multiple [Arguments] attributes can be applied to the same test method

Assertions

  • Use await Assert.That(value).IsEqualTo(expected) for value equality
  • Use await Assert.That(value).IsSameReferenceAs(expected) for reference equality
  • Use await Assert.That(value).IsTrue() or await Assert.That(value).IsFalse() for boolean conditions
  • Use await Assert.That(collection).Contains(item) or await Assert.That(collection).DoesNotContain(item) for collections
  • Use await Assert.That(value).Matches(pattern) for regex pattern matching
  • Use await Assert.That(action).Throws<TException>() or await Assert.That(asyncAction).ThrowsAsync<TException>() to test exceptions
  • Chain assertions with .And operator: await Assert.That(value).IsNotNull().And.IsEqualTo(expected)
  • Use .Or operator for alternative conditions: await Assert.That(value).IsEqualTo(1).Or.IsEqualTo(2)
  • Use .Within(tolerance) for DateTime and numeric comparisons with tolerance
  • All assertions are asynchronous and must be awaited

Advanced Features

  • Use [Repeat(n)] to repeat tests multiple times
  • Use [Retry(n)] for automatic retry on failure
  • Use [ParallelLimit<T>] to control parallel execution limits
  • Use [Skip("reason")] to skip tests conditionally
  • Use [DependsOn(nameof(OtherTest))] to create test dependencies
  • Use [Timeout(milliseconds)] to set test timeouts
  • Create custom attributes by extending TUnit's base attributes

Test Organization

  • Group tests by feature or component
  • Use [Category("CategoryName")] for test categorization
  • Use [DisplayName("Custom Test Name")] for custom test names
  • Consider using TestContext for test diagnostics and information
  • Use conditional attributes like custom [WindowsOnly] for platform-specific tests

Performance and Parallel Execution

  • TUnit runs tests in parallel by default (unlike xUnit which requires explicit configuration)
  • Use [NotInParallel] to disable parallel execution for specific tests
  • Use [ParallelLimit<T>] with custom limit classes to control concurrency
  • Tests within the same class run sequentially by default
  • Use [Repeat(n)] with [ParallelLimit<T>] for load testing scenarios

Migration from xUnit

  • Replace [Fact] with [Test]
  • Replace [Theory] with [Test] and use [Arguments] for data
  • Replace [InlineData] with [Arguments]
  • Replace [MemberData] with [MethodData]
  • Replace Assert.Equal with await Assert.That(actual).IsEqualTo(expected)
  • Replace Assert.True with await Assert.That(condition).IsTrue()
  • Replace Assert.Throws<T> with await Assert.That(action).Throws<T>()
  • Replace constructor/IDisposable with [Before(Test)]/[After(Test)]
  • Replace IClassFixture<T> with [Before(Class)]/[After(Class)]

Why TUnit over xUnit?

TUnit offers a modern, fast, and flexible testing experience with advanced features not present in xUnit, such as asynchronous assertions, more refined lifecycle hooks, and improved data-driven testing capabilities. TUnit's fluent assertions provide clearer and more expressive test validation, making it especially suitable for complex .NET projects.

Source

git clone https://github.com/github/awesome-copilot/blob/main/plugins/csharp-dotnet-development/skills/csharp-tunit/SKILL.mdView on GitHub

Overview

Learn to write effective TUnit tests for C# projects, including standard and data-driven approaches. This guide covers project setup, test structure, lifecycle hooks, and advanced features to produce reliable, maintainable tests in .NET 8+.

How This Skill Works

TUnit tests use the [Test] attribute for methods and fluent assertions with await Assert.That(...). Tests live in a dedicated [ProjectName].Tests project; follow the Arrange-Act-Assert pattern and name tests like Method_Scenario_ExpectedBehavior. Data-driven tests leverage [Arguments], [MethodData], and related sources, with optional class-based data or custom ITestDataSource implementations.

When to Use It

  • Create standard tests that verify a single behavior using AAA in a dedicated [ProjectName].Tests project.
  • Implement data-driven tests to cover multiple inputs with meaningful parameter names.
  • Organize tests to be independent, idempotent, and easily runnable with dotnet test.
  • Leverage lifecycle hooks ([Before], [After], and assembly/class/test scopes) to manage shared context.
  • Explore advanced features like Repeat, Retry, Parallel limits, Skip, and test timeouts when needed.

Quick Start

  1. Step 1: Create a separate test project named [ProjectName].Tests and reference the TUnit packages.
  2. Step 2: Write a standard test using [Test], apply AAA, and place tests in a class named [Class]Tests.
  3. Step 3: Add a data-driven test using [Arguments]/[MethodData]/[ClassData], then run dotnet test to verify results.

Best Practices

  • Keep each test focused on a single behavior and a single assertion where possible.
  • Name tests using the pattern MethodName_Scenario_ExpectedBehavior and follow AAA.
  • Use await Assert.That(...) with fluent assertions for clear, asynchronous checks.
  • Ensure tests are independent and idempotent; minimize interdependencies unless using [DependsOn].
  • Organize tests by feature, use a separate [ProjectName].Tests project, and apply lifecycle hooks for setup/teardown.

Example Use Cases

  • CalculatorTests for Calculator.Add with data-driven inputs
  • UserService_CreateUser_ShouldSucceed in UserServiceTests
  • StringValidator_IsMatchRegex_ShouldDetectValidPatterns
  • AsyncRepository_GetById_ShouldReturnNullWhenNotFound
  • DataProcessing_ValidateRecords_WhenMixedValidAndInvalid_ShouldFlagErrors

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers