diff --git a/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs b/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs index 319410a9c1..3916bebdfd 100644 --- a/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs +++ b/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs @@ -321,12 +321,18 @@ private static void GenerateTestMetadata(CodeWriter writer, TestMethodMetadata t .Any(a => a.AttributeClass?.Name == "MethodDataSourceAttribute" && InferClassTypesFromMethodDataSource(compilation, testMethod, a) != null); - if (hasTypedDataSource || hasGenerateGenericTest || testMethod.IsGenericMethod || hasClassArguments || hasTypedDataSourceForGenericType || hasMethodArgumentsForGenericType || hasMethodDataSourceForGenericType) + // Check for class-level data sources that could help resolve generic type arguments + var hasClassDataSources = testMethod.IsGenericType && testMethod.TypeSymbol.GetAttributesIncludingBaseTypes() + .Any(a => DataSourceAttributeHelper.IsDataSourceAttribute(a.AttributeClass)); + + if (hasTypedDataSource || hasGenerateGenericTest || testMethod.IsGenericMethod || hasClassArguments || hasTypedDataSourceForGenericType || hasMethodArgumentsForGenericType || hasMethodDataSourceForGenericType || hasClassDataSources) { GenerateGenericTestWithConcreteTypes(writer, testMethod, className, uniqueClassName); } else { + // For generic classes with no way to resolve type arguments, this will generate + // GenericTestMetadata that the engine will fail with a clear error message GenerateTestMetadataInstance(writer, testMethod, className, uniqueClassName); } } diff --git a/TUnit.Engine/Building/TestBuilder.cs b/TUnit.Engine/Building/TestBuilder.cs index 4c767710e4..04ca0732b1 100644 --- a/TUnit.Engine/Building/TestBuilder.cs +++ b/TUnit.Engine/Building/TestBuilder.cs @@ -397,7 +397,7 @@ await _objectLifecycleService.RegisterObjectAsync( if (metadata.TestClassType.IsGenericTypeDefinition && resolvedClassGenericArgs.Length == 0) { - throw new InvalidOperationException($"Cannot create instance of generic type {metadata.TestClassType.Name} with empty type arguments"); + throw new InvalidOperationException($"Cannot create test for generic class '{metadata.TestClassType.Name}': No type arguments could be inferred. Add [GenerateGenericTest] to the class, or use a data source (like [ClassDataSource] or [Arguments]) that provides constructor arguments to infer the generic type arguments from."); } var basicSkipReason = GetBasicSkipReason(metadata, attributes); @@ -1727,7 +1727,7 @@ private Task CreateInstanceForMethodDataSources( if (metadata.TestClassType.IsGenericTypeDefinition && resolvedClassGenericArgs.Length == 0) { - throw new InvalidOperationException($"Cannot create instance of generic type {metadata.TestClassType.Name} with empty type arguments"); + throw new InvalidOperationException($"Cannot create test for generic class '{metadata.TestClassType.Name}': No type arguments could be inferred. Add [GenerateGenericTest] to the class, or use a data source (like [ClassDataSource] or [Arguments]) that provides constructor arguments to infer the generic type arguments from."); } // Create instance factory diff --git a/TUnit.Engine/Services/TestGenericTypeResolver.cs b/TUnit.Engine/Services/TestGenericTypeResolver.cs index 76c0a5a74c..5a669fcdc8 100644 --- a/TUnit.Engine/Services/TestGenericTypeResolver.cs +++ b/TUnit.Engine/Services/TestGenericTypeResolver.cs @@ -97,7 +97,9 @@ private static Type[] ResolveClassGenericArguments( // For classes with parameterless constructors, throw a specific exception // that can be caught and handled by the caller throw new GenericTypeResolutionException( - $"Could not resolve type for generic parameter(s) of type '{genericClassType.Name}' from constructor arguments. Type inference from method data may be required."); + $"Cannot create test for generic class '{genericClassType.Name}': No type arguments could be inferred. " + + $"Add [GenerateGenericTest] to the class, or use a data source " + + $"(like [ClassDataSource] or [Arguments]) that provides constructor arguments to infer the generic type arguments from."); } // Resolve all generic parameters @@ -108,7 +110,8 @@ private static Type[] ResolveClassGenericArguments( if (!typeMapping.TryGetValue(genericParam, out var resolvedType)) { throw new GenericTypeResolutionException( - $"Could not resolve type for generic parameter '{genericParam.Name}' of type '{genericClassType.Name}'"); + $"Cannot create test for generic class '{genericClassType.Name}': Could not resolve type for generic parameter '{genericParam.Name}'. " + + $"Add [GenerateGenericTest] to the class, or use a data source that provides constructor arguments to infer the generic type arguments from."); } resolvedTypes[i] = resolvedType; } diff --git a/TUnit.Example.Asp.Net.TestProject/TUnit.Example.Asp.Net.TestProject.csproj b/TUnit.Example.Asp.Net.TestProject/TUnit.Example.Asp.Net.TestProject.csproj index e22918675d..5d5406cef4 100644 --- a/TUnit.Example.Asp.Net.TestProject/TUnit.Example.Asp.Net.TestProject.csproj +++ b/TUnit.Example.Asp.Net.TestProject/TUnit.Example.Asp.Net.TestProject.csproj @@ -4,6 +4,7 @@ net10.0 + $(InterceptorsNamespaces);Microsoft.AspNetCore.OpenApi.Generated