Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -438,4 +438,4 @@ doc/plans/
*.nettrace

# Git worktrees
.worktrees/
.worktrees/.worktrees/
88 changes: 88 additions & 0 deletions TUnit.Engine.Tests/GenericMethodWithDataSourceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using Shouldly;
using TUnit.Engine.Tests.Enums;

namespace TUnit.Engine.Tests;

/// <summary>
/// Tests that verify generic methods with [GenerateGenericTest] and [MethodDataSource] attributes
/// are properly discovered and executed.
/// </summary>
public class GenericMethodWithDataSourceTests(TestMode testMode) : InvokableTestBase(testMode)
{
[Test]
public async Task NonGenericClassWithGenericMethodAndDataSource_Should_Generate_Tests()
{
// This test verifies that a non-generic class with a generic method that has both
// [GenerateGenericTest(typeof(int))] and [GenerateGenericTest(typeof(double))]
// combined with [MethodDataSource(nameof(GetStrings))] generates 4 tests:
// - GenericMethodWithDataSource<int>("hello")
// - GenericMethodWithDataSource<int>("world")
// - GenericMethodWithDataSource<double>("hello")
// - GenericMethodWithDataSource<double>("world")
await RunTestsWithFilter(
"/*/*/Bug4440_NonGenericClassWithGenericMethodAndDataSource/*",
[
result => result.ResultSummary.Outcome.ShouldBe("Completed"),
result => result.ResultSummary.Counters.Total.ShouldBe(4),
result => result.ResultSummary.Counters.Passed.ShouldBe(4),
result => result.ResultSummary.Counters.Failed.ShouldBe(0),
result => result.ResultSummary.Counters.NotExecuted.ShouldBe(0)
]);
}

[Test]
public async Task GenericClassWithMethodDataSource_Should_Generate_Tests()
{
// Generic class with 2 types (string, object) and data source with 3 items (1, 2, 3)
// Expected: 2 class types × 3 data items = 6 tests
// The data source values differentiate the test names, so all 6 are unique.
await RunTestsWithFilter(
"/*/*/Bug4440_GenericClassWithMethodDataSource*/*",
[
result => result.ResultSummary.Outcome.ShouldBe("Completed"),
result => result.ResultSummary.Counters.Total.ShouldBe(6),
result => result.ResultSummary.Counters.Passed.ShouldBe(6),
result => result.ResultSummary.Counters.Failed.ShouldBe(0),
result => result.ResultSummary.Counters.NotExecuted.ShouldBe(0)
]);
}

[Test]
public async Task FullyGenericWithDataSources_Should_Generate_Tests()
{
// Cartesian product: 2 class types × 2 method types × 2 data items = 8 tests
// - Bug4440_GenericClassGenericMethodWithDataSources<string>.CartesianProduct<int>(true)
// - Bug4440_GenericClassGenericMethodWithDataSources<string>.CartesianProduct<int>(false)
// - Bug4440_GenericClassGenericMethodWithDataSources<string>.CartesianProduct<double>(true)
// - Bug4440_GenericClassGenericMethodWithDataSources<string>.CartesianProduct<double>(false)
// - Bug4440_GenericClassGenericMethodWithDataSources<object>.CartesianProduct<int>(true)
// - Bug4440_GenericClassGenericMethodWithDataSources<object>.CartesianProduct<int>(false)
// - Bug4440_GenericClassGenericMethodWithDataSources<object>.CartesianProduct<double>(true)
// - Bug4440_GenericClassGenericMethodWithDataSources<object>.CartesianProduct<double>(false)
await RunTestsWithFilter(
"/*/*/Bug4440_GenericClassGenericMethodWithDataSources*/*",
[
result => result.ResultSummary.Outcome.ShouldBe("Completed"),
result => result.ResultSummary.Counters.Total.ShouldBe(8),
result => result.ResultSummary.Counters.Passed.ShouldBe(8),
result => result.ResultSummary.Counters.Failed.ShouldBe(0),
result => result.ResultSummary.Counters.NotExecuted.ShouldBe(0)
]);
}

[Test]
public async Task GenericMethodWithoutDataSource_Should_Work()
{
// Generic method with 3 type arguments (int, string, object) but NO data source.
// Expected: 3 tests with unique names: GenericMethod_Should_Work<Int32>, <String>, <Object>
await RunTestsWithFilter(
"/*/*/Bug4440_NonGenericClassWithGenericMethod/*",
[
result => result.ResultSummary.Outcome.ShouldBe("Completed"),
result => result.ResultSummary.Counters.Total.ShouldBe(3),
result => result.ResultSummary.Counters.Passed.ShouldBe(3),
result => result.ResultSummary.Counters.Failed.ShouldBe(0),
result => result.ResultSummary.Counters.NotExecuted.ShouldBe(0)
]);
}
}
39 changes: 39 additions & 0 deletions TUnit.Engine/Building/TestBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1165,6 +1165,30 @@ private static bool IsDataCompatibleWithExpectedTypes(TestMetadata metadata, obj
return true; // No specific types expected, allow all data
}

// Check if any method parameter actually uses the method's generic type parameters.
// If none of the parameters use T, then data compatibility with the generic type doesn't matter.
// This is important for methods like GenericMethod<T>(string input) where the parameter
// is a concrete type (string) and doesn't depend on the generic type T.
if (metadata.GenericMethodTypeArguments is { Length: > 0 })
{
var anyParameterUsesMethodGeneric = false;
foreach (var param in metadata.MethodMetadata.Parameters)
{
if (ParameterUsesMethodGenericType(param.TypeInfo))
{
anyParameterUsesMethodGeneric = true;
break;
}
}

if (!anyParameterUsesMethodGeneric)
{
// None of the method parameters use the method's generic type parameters.
// The data doesn't need to match the generic types - allow all data.
return true;
}
}

// For generic methods, we need to check if the data types match the expected types
// The key is to determine what type of data this data source produces

Expand Down Expand Up @@ -1260,6 +1284,21 @@ private static bool IsDataCompatibleWithExpectedTypes(TestMetadata metadata, obj
return false;
}

/// <summary>
/// Checks if a parameter's type involves a method generic type parameter.
/// Returns true for parameters like T, List&lt;T&gt;, etc. where T is a method generic parameter.
/// Returns false for concrete types like string, int, etc.
/// </summary>
private static bool ParameterUsesMethodGenericType(TypeInfo? typeInfo)
{
return typeInfo switch
{
GenericParameter { IsMethodParameter: true } => true,
ConstructedGeneric cg => cg.TypeArguments.Any(ParameterUsesMethodGenericType),
_ => false
};
}

private static Type? GetExpectedTypeForParameter(ParameterMetadata param, Type[] genericTypeArgs)
{
// If it's a direct generic parameter (e.g., T)
Expand Down
Loading
Loading