Skillshub csharp-tunit
Get best practices for TUnit unit testing, including data-driven tests
install
source · Clone the upstream repo
git clone https://github.com/ComeOnOliver/skillshub
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/github/awesome-copilot/csharp-tunit" ~/.claude/skills/comeonoliver-skillshub-csharp-tunit && rm -rf "$T"
manifest:
skills/github/awesome-copilot/csharp-tunit/SKILL.mdsource content
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.,
forCalculatorTests
)Calculator - Use .NET SDK test commands:
for running testsdotnet test - TUnit requires .NET 8.0 or higher
Test Structure
- No test class attributes required (like xUnit/NUnit)
- Use
attribute for test methods (not[Test]
like xUnit)[Fact] - Follow the Arrange-Act-Assert (AAA) pattern
- Name tests using the pattern
MethodName_Scenario_ExpectedBehavior - Use lifecycle hooks:
for setup and[Before(Test)]
for teardown[After(Test)] - Use
and[Before(Class)]
for shared context between tests in a class[After(Class)] - Use
and[Before(Assembly)]
for shared context across test classes[After(Assembly)] - TUnit supports advanced lifecycle hooks like
and[Before(TestSession)][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
attribute if needed)[DependsOn]
Data-Driven Tests
- Use
attribute for inline test data (equivalent to xUnit's[Arguments]
)[InlineData] - Use
for method-based test data (equivalent to xUnit's[MethodData]
)[MemberData] - Use
for class-based test data[ClassData] - Create custom data sources by implementing
ITestDataSource - Use meaningful parameter names in data-driven tests
- Multiple
attributes can be applied to the same test method[Arguments]
Assertions
- Use
for value equalityawait Assert.That(value).IsEqualTo(expected) - Use
for reference equalityawait Assert.That(value).IsSameReferenceAs(expected) - Use
orawait Assert.That(value).IsTrue()
for boolean conditionsawait Assert.That(value).IsFalse() - Use
orawait Assert.That(collection).Contains(item)
for collectionsawait Assert.That(collection).DoesNotContain(item) - Use
for regex pattern matchingawait Assert.That(value).Matches(pattern) - Use
orawait Assert.That(action).Throws<TException>()
to test exceptionsawait Assert.That(asyncAction).ThrowsAsync<TException>() - Chain assertions with
operator:.Andawait Assert.That(value).IsNotNull().And.IsEqualTo(expected) - Use
operator for alternative conditions:.Orawait Assert.That(value).IsEqualTo(1).Or.IsEqualTo(2) - Use
for DateTime and numeric comparisons with tolerance.Within(tolerance) - All assertions are asynchronous and must be awaited
Advanced Features
- Use
to repeat tests multiple times[Repeat(n)] - Use
for automatic retry on failure[Retry(n)] - Use
to control parallel execution limits[ParallelLimit<T>] - Use
to skip tests conditionally[Skip("reason")] - Use
to create test dependencies[DependsOn(nameof(OtherTest))] - Use
to set test timeouts[Timeout(milliseconds)] - Create custom attributes by extending TUnit's base attributes
Test Organization
- Group tests by feature or component
- Use
for test categorization[Category("CategoryName")] - Use
for custom test names[DisplayName("Custom Test Name")] - Consider using
for test diagnostics and informationTestContext - Use conditional attributes like custom
for platform-specific tests[WindowsOnly]
Performance and Parallel Execution
- TUnit runs tests in parallel by default (unlike xUnit which requires explicit configuration)
- Use
to disable parallel execution for specific tests[NotInParallel] - Use
with custom limit classes to control concurrency[ParallelLimit<T>] - Tests within the same class run sequentially by default
- Use
with[Repeat(n)]
for load testing scenarios[ParallelLimit<T>]
Migration from xUnit
- Replace
with[Fact][Test] - Replace
with[Theory]
and use[Test]
for data[Arguments] - Replace
with[InlineData][Arguments] - Replace
with[MemberData][MethodData] - Replace
withAssert.Equalawait Assert.That(actual).IsEqualTo(expected) - Replace
withAssert.Trueawait Assert.That(condition).IsTrue() - Replace
withAssert.Throws<T>await Assert.That(action).Throws<T>() - Replace constructor/IDisposable with
/[Before(Test)][After(Test)] - Replace
withIClassFixture<T>
/[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.