CombinedDataSources
Overviewâ
The [CombinedDataSources] attribute enables you to apply different data source attributes to individual parameters, creating test cases through Cartesian product combination. This provides maximum flexibility when you need different parameters to be generated by different data sources.
Key Featuresâ
- â
Apply ANY
IDataSourceAttributeto individual parameters - â
Mix
[Arguments],[MethodDataSource],[ClassDataSource], and custom data sources - â Automatic Cartesian product generation
- â Full AOT/Native compilation support
- â Works in both source-generated and reflection modes
Comparison with MatrixDataSourceâ
| Feature | MatrixDataSource | CombinedDataSources |
|---|---|---|
| Parameter-level attributes | [Matrix] only | ANY IDataSourceAttribute |
| Combination strategy | Cartesian product | Cartesian product |
| Data source types | Matrix-specific | All TUnit data sources |
| Use case | Simple matrix combinations | Complex, mixed data scenarios |
Basic Usageâ
Simple Arguments Mixingâ
[Test]
[CombinedDataSources]
public async Task SimpleTest(
[Arguments(1, 2, 3)] int x,
[Arguments("a", "b")] string y)
{
// Creates 3 Ã 2 = 6 test cases:
// (1, "a"), (1, "b"), (2, "a"), (2, "b"), (3, "a"), (3, "b")
await Assert.That(x).IsIn([1, 2, 3]);
await Assert.That(y).IsIn(["a", "b"]);
}
Mixing Arguments with MethodDataSourceâ
public static IEnumerable<string> GetStrings()
{
yield return "Hello";
yield return "World";
}
[Test]
[CombinedDataSources]
public async Task MixedDataSources(
[Arguments(1, 2)] int x,
[MethodDataSource(nameof(GetStrings))] string y)
{
// Creates 2 Ã 2 = 4 test cases:
// (1, "Hello"), (1, "World"), (2, "Hello"), (2, "World")
await Assert.That(x).IsIn([1, 2]);
await Assert.That(y).IsIn(["Hello", "World"]);
}
Advanced Scenariosâ
Three Parameters from Different Sourcesâ
public static IEnumerable<int> GetNumbers()
{
yield return 10;
yield return 20;
yield return 30;
}
[Test]
[CombinedDataSources]
public async Task ThreeWayMix(
[Arguments(1, 2)] int x,
[MethodDataSource(nameof(GetNumbers))] int y,
[Arguments(true, false)] bool z)
{
// Creates 2 Ã 3 Ã 2 = 12 test cases
await Assert.That(x).IsIn([1, 2]);
await Assert.That(y).IsIn([10, 20, 30]);
await Assert.That(z).IsIn([true, false]);
}
Multiple Data Sources on Same Parameterâ
You can apply multiple data source attributes to a single parameter - all values will be combined:
[Test]
[CombinedDataSources]
public async Task MultipleSourcesPerParameter(
[Arguments(1, 2)]
[Arguments(3, 4)] int x,
[Arguments("a")] string y)
{
// Creates (2 + 2) Ã 1 = 4 test cases
// x can be: 1, 2, 3, or 4
await Assert.That(x).IsIn([1, 2, 3, 4]);
await Assert.That(y).IsEqualTo("a");
}
Using ClassDataSourceâ
public class MyTestData
{
public int Value { get; set; }
public string Name { get; set; } = string.Empty;
}
[Test]
[CombinedDataSources]
public async Task WithClassDataSource(
[Arguments(1, 2)] int x,
[ClassDataSource<MyTestData>] MyTestData obj)
{
// Creates 2 Ã 1 = 2 test cases
await Assert.That(x).IsIn([1, 2]);
await Assert.That(obj).IsNotNull();
}
Complex Type Combinationsâ
[Test]
[CombinedDataSources]
public async Task DifferentTypes(
[Arguments(1, 2)] int intVal,
[Arguments("a", "b")] string stringVal,
[Arguments(1.5, 2.5)] double doubleVal,
[Arguments(true, false)] bool boolVal,
[Arguments('x', 'y')] char charVal)
{
// Creates 2 Ã 2 Ã 2 Ã 2 Ã 2 = 32 test cases
// All combinations of the parameter values
}
Cartesian Product Behaviorâ
The [CombinedDataSources] generates test cases using Cartesian product - every combination of parameter values is tested.
Example Calculationâ
Given:
- Parameter
a:[Arguments(1, 2, 3)]â 3 values - Parameter
b:[Arguments("x", "y")]â 2 values - Parameter
c:[Arguments(true, false)]â 2 values
Total test cases: 3 Ã 2 Ã 2 = 12
Generated combinations:
(1, "x", true), (1, "x", false), (1, "y", true), (1, "y", false),
(2, "x", true), (2, "x", false), (2, "y", true), (2, "y", false),
(3, "x", true), (3, "x", false), (3, "y", true), (3, "y", false)
Supported Data Source Attributesâ
The following attributes can be applied to parameters with [CombinedDataSources]:
Built-in Attributesâ
| Attribute | Description | Example |
|---|---|---|
[Arguments] | Inline values | [Arguments(1, 2, 3)] |
[MethodDataSource] | Values from a method | [MethodDataSource(nameof(GetData))] |
[MethodDataSource<T>] | Typed method data source | [MethodDataSource<MyClass>(nameof(GetData))] |
[ClassDataSource<T>] | Instance generation | [ClassDataSource<MyClass>] |
[ClassDataSource] | Dynamic type instances | [ClassDataSource(typeof(MyClass))] |
Custom Data Sourcesâ
Any attribute implementing IDataSourceAttribute can be used:
public class CustomDataSourceAttribute : DataSourceGeneratorAttribute<string>
{
protected override IEnumerable<Func<string>> GenerateDataSources(
DataGeneratorMetadata metadata)
{
yield return () => "Custom1";
yield return () => "Custom2";
}
}
[Test]
[CombinedDataSources]
public async Task WithCustomDataSource(
[Arguments(1, 2)] int x,
[CustomDataSource] string y)
{
// Creates 2 Ã 2 = 4 test cases
}
Best Practicesâ
â DOâ
- Use descriptive parameter names to make test output clear
- Keep parameter counts reasonable (< 5 parameters typically)
- Be mindful of Cartesian product size - 5 params à 10 values each = 100,000 tests!
- Group related tests in the same test class
- Use assertions to validate parameter values when debugging
â DON'Tâ
- Don't create excessive test cases - Be aware of exponential growth
- Don't mix with method-level data sources - Use one approach per test method
- Don't forget to test edge cases like null values
- Don't leave parameters without data sources - All parameters must have at least one data source attribute
Performance Considerationsâ
Test Case Growthâ
Be aware of exponential growth with multiple parameters:
| Parameters | Values Each | Total Tests |
|---|---|---|
| 2 | 3 | 9 |
| 3 | 3 | 27 |
| 4 | 3 | 81 |
| 5 | 3 | 243 |
| 3 | 10 | 1,000 |
| 4 | 10 | 10,000 |
Optimization Tipsâ
- Reduce parameter value sets when possible
- Use focused test methods - test one concept per method
- Consider using
[Matrix]for simpler scenarios if you don't need mixed data sources - Leverage test parallelization - TUnit runs tests in parallel by default
Edge Cases and Error Handlingâ
Missing Data Sourceâ
// â ERROR: Parameter 'y' has no data source
[Test]
[CombinedDataSources]
public async Task MissingDataSource(
[Arguments(1, 2)] int x,
int y) // No data source attribute!
{
// This will fail during test initialization
}
Error: Parameter 'y' has no data source attributes. All parameters must have at least one IDataSourceAttribute when using [CombinedDataSources].
No Parametersâ
// â ERROR: No parameters with data sources
[Test]
[CombinedDataSources]
public async Task NoParameters()
{
// This will fail
}
Error: [CombinedDataSources] only supports parameterised tests
Nullable Typesâ
Nullable types are fully supported:
[Test]
[CombinedDataSources]
public async Task NullableTypes(
[Arguments(1, 2, null)] int? nullableInt,
[Arguments("a", null)] string? nullableString)
{
// Creates 3 Ã 2 = 6 test cases including nulls
if (nullableInt.HasValue)
{
await Assert.That(nullableInt.Value).IsIn([1, 2]);
}
}
Comparison with Other Approachesâ
vs. Method-Level [Arguments]â
Method-Level:
[Test]
[Arguments(1, "a")]
[Arguments(2, "b")]
public async Task OldWay(int x, string y)
{
// Must manually specify every combination
// Only creates 2 test cases
}
MixedParameters:
[Test]
[CombinedDataSources]
public async Task NewWay(
[Arguments(1, 2)] int x,
[Arguments("a", "b")] string y)
{
// Automatically creates all 4 combinations
}
vs. MatrixDataSourceâ
Matrix:
[Test]
[MatrixDataSource]
public async Task MatrixWay(
[Matrix(1, 2)] int x,
[Matrix("a", "b")] string y)
{
// Limited to Matrix attribute only
}
MixedParameters:
[Test]
[CombinedDataSources]
public async Task MixedWay(
[Arguments(1, 2)] int x,
[MethodDataSource(nameof(GetStrings))] string y)
{
// Can mix any data source types!
}
AOT/Native Compilationâ
[CombinedDataSources] is fully compatible with AOT and Native compilation. The attribute uses proper trimming annotations and works in both source-generated and reflection modes.
Examples from Real-World Scenariosâ
Testing API Endpoints with Different Configurationsâ
public static IEnumerable<HttpMethod> GetHttpMethods()
{
yield return HttpMethod.Get;
yield return HttpMethod.Post;
yield return HttpMethod.Put;
}
[Test]
[CombinedDataSources]
public async Task ApiEndpoint_ResponseCodes(
[MethodDataSource(nameof(GetHttpMethods))] HttpMethod method,
[Arguments("/api/users", "/api/products")] string endpoint,
[Arguments(200, 404)] int expectedStatusCode)
{
// Tests all combinations of HTTP methods, endpoints, and expected codes
// 3 Ã 2 Ã 2 = 12 test cases
}
Database Query Testingâ
public class QueryParameters
{
public int PageSize { get; set; }
public string SortOrder { get; set; } = string.Empty;
}
[Test]
[CombinedDataSources]
public async Task Database_Pagination(
[Arguments(10, 20, 50)] int pageSize,
[Arguments("asc", "desc")] string sortOrder,
[Arguments(true, false)] bool includeDeleted)
{
// Tests all pagination combinations
// 3 Ã 2 Ã 2 = 12 test cases
}
Troubleshootingâ
Issue: Too Many Test Cases Generatedâ
Problem: Test run takes too long due to exponential growth
Solution:
- Reduce the number of values per parameter
- Split into multiple focused test methods
- Use more specific test scenarios
Issue: Data Source Returns No Valuesâ
Problem: A parameter's data source returns an empty enumerable
Solution:
- Ensure data source methods return at least one value
- Check that the method is static/accessible
- Verify method signature matches expected format
Issue: Parameter Type Mismatchâ
Problem: Data source returns wrong type for parameter
Solution:
- Ensure data source return type matches parameter type
- Use typed data sources:
[MethodDataSource<MyClass>] - Check that generated values can be cast to parameter type
See Alsoâ
- MatrixDataSource Documentation
- MethodDataSource Documentation
- ClassDataSource Documentation
- Arguments Attribute Documentation
Version Historyâ
- v1.0.0 - Initial release
- Parameter-level data source support
- Cartesian product generation
- Support for all
IDataSourceAttributeimplementations - Full AOT compatibility