Test Data Row Metadata
When using data sources like [MethodDataSource] or [ClassDataSource], you may want to customize individual test cases with specific display names, skip reasons, or categories. TUnit provides the TestDataRow<T> wrapper type for this purpose.
Basic Usageâ
Wrap your test data in TestDataRow<T> to add metadata:
using TUnit.Core;
public static class LoginTestData
{
public static IEnumerable<TestDataRow<(string Username, string Password)>> GetCredentials()
{
yield return new(("admin", "secret123"), DisplayName: "Admin login");
yield return new(("guest", "guest"), DisplayName: "Guest login");
yield return new(("", ""), DisplayName: "Empty credentials", Skip: "Not implemented yet");
}
}
public class LoginTests
{
[Test]
[MethodDataSource(typeof(LoginTestData), nameof(LoginTestData.GetCredentials))]
public async Task TestLogin(string username, string password)
{
// Test implementation
}
}
Available Propertiesâ
TestDataRow<T> provides these optional properties:
| Property | Type | Description |
|---|---|---|
DisplayName | string? | Custom name shown in test output and IDE |
Skip | string? | Skip reason; when set, the test is skipped |
Categories | string[]? | Categories for filtering tests |
Display Name Substitutionâ
Display names support parameter substitution using $paramName or positional $arg1, $arg2 syntax:
public static IEnumerable<TestDataRow<(int A, int B, int Expected)>> GetMathData()
{
yield return new((2, 3, 5), DisplayName: "Adding $A + $B = $Expected");
yield return new((10, 5, 15), DisplayName: "$arg1 plus $arg2 equals $arg3");
}
The placeholders are replaced with the actual argument values at test discovery time.
Working with Complex Typesâ
For complex objects, wrap the entire object:
public record UserTestCase(string Email, bool IsAdmin, string ExpectedRole);
public static class UserTestData
{
public static IEnumerable<TestDataRow<UserTestCase>> GetUserCases()
{
yield return new(
new UserTestCase("admin@test.com", true, "Administrator"),
DisplayName: "Admin user gets admin role",
Categories: ["Admin", "Roles"]
);
yield return new(
new UserTestCase("user@test.com", false, "Standard"),
DisplayName: "Regular user gets standard role"
);
}
}
public class UserRoleTests
{
[Test]
[MethodDataSource(typeof(UserTestData), nameof(UserTestData.GetUserCases))]
public async Task TestUserRole(UserTestCase testCase)
{
// testCase.Email, testCase.IsAdmin, testCase.ExpectedRole
}
}
With Func<T> for Reference Typesâ
When returning reference types, combine with Func<T> to ensure fresh instances:
public static IEnumerable<TestDataRow<Func<HttpClient>>> GetHttpClients()
{
yield return new(
() => new HttpClient { BaseAddress = new Uri("https://api.example.com") },
DisplayName: "Production API client"
);
yield return new(
() => new HttpClient { BaseAddress = new Uri("https://staging.example.com") },
DisplayName: "Staging API client"
);
}
Skipping Individual Test Casesâ
Use the Skip property to skip specific test cases while keeping others active:
public static IEnumerable<TestDataRow<(string Browser, string Version)>> GetBrowsers()
{
yield return new(("Chrome", "120"), DisplayName: "Chrome latest");
yield return new(("Firefox", "121"), DisplayName: "Firefox latest");
yield return new(("Safari", "17"), DisplayName: "Safari", Skip: "Safari not installed on CI");
yield return new(("Edge", "120"), DisplayName: "Edge latest");
}
Categorizing Test Casesâ
Apply categories to individual test cases for filtering:
public static IEnumerable<TestDataRow<(string Endpoint, string Method)>> GetApiEndpoints()
{
yield return new(
("/users", "GET"),
DisplayName: "List users",
Categories: ["API", "Users", "ReadOnly"]
);
yield return new(
("/users", "POST"),
DisplayName: "Create user",
Categories: ["API", "Users", "Write"]
);
yield return new(
("/admin/config", "PUT"),
DisplayName: "Update config",
Categories: ["API", "Admin", "Write"]
);
}
Run only specific categories:
dotnet run -- --filter "Category=Admin"
With ClassDataSourceâ
TestDataRow<T> works with [ClassDataSource] too:
public class DatabaseTestData : IEnumerable<TestDataRow<(string ConnectionString, string DbName)>>
{
public IEnumerator<TestDataRow<(string ConnectionString, string DbName)>> GetEnumerator()
{
yield return new(
("Server=localhost;Database=TestDb1", "TestDb1"),
DisplayName: "Local database"
);
yield return new(
("Server=remote;Database=TestDb2", "TestDb2"),
DisplayName: "Remote database",
Skip: "Remote server unavailable"
);
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
public class DatabaseTests
{
[Test]
[ClassDataSource<DatabaseTestData>]
public async Task TestDatabaseConnection(string connectionString, string dbName)
{
// Test implementation
}
}
Universal Data Source Supportâ
TestDataRow<T> works with any data source that implements IDataSourceAttribute, including custom data source attributes. TUnit automatically detects and unwraps TestDataRow<T> instances, extracting the metadata regardless of the data source type.
See Alsoâ
- Arguments Attribute - For compile-time constant data with inline metadata
- Method Data Sources - For dynamic test data generation
- Class Data Sources - For class-based test data
- Display Names - For global display name formatting