XUnit Theory Test Using Generics

In xUnit, I can have a Theory test that uses generics in this form:

 [Theory] [MemberData(SomeScenario)] public void TestMethod<T>(T myType) { Assert.Equal(typeof(double), typeof(T)); } public static IEnumerable<object[]> SomeScenario() { yield return new object[] { 1.23D }; } 

Which will give me the general parameter T as double . Is it possible to use MemberData to specify a type type parameter for a test with a type signature:

 [Theory] [MemberData(SomeTypeScenario)] public void TestMethod<T>() { Assert.Equal(typeof(double), typeof(T)); } 

If this is not possible with MemberData or any other attribute provided (which I suspect is not the case), is it possible to create an attribute for Xunit that can do this? Perhaps something similar to defining types in the Scenarios method and using reflection in a similar way for Jon Skeet here: Generics in C #, using the type of the variable as a parameter

+6
source share
1 answer

Instead, you can include Type as an input parameter. For instance:.

 [Theory] [MemberData(SomeTypeScenario)] public void TestMethod(Type type) { Assert.Equal(typeof(double), type); } public static IEnumerable<object[]> SomeScenario() { yield return new object[] { typeof(double) }; } 

No need to go with generics in xunit.

Edit (if you really need shared files)

1) You need to subclass ITestMethod to save general information about the method, you also need to implement IXunitSerializable

 // assuming namespace Contosco public class GenericTestMethod : MarshalByRefObject, ITestMethod, IXunitSerializable { public IMethodInfo Method { get; set; } public ITestClass TestClass { get; set; } public ITypeInfo GenericArgument { get; set; } /// <summary /> [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] public GenericTestMethod() { } public GenericTestMethod(ITestClass @class, IMethodInfo method, ITypeInfo genericArgument) { this.Method = method; this.TestClass = @class; this.GenericArgument = genericArgument; } public void Serialize(IXunitSerializationInfo info) { info.AddValue("MethodName", (object) this.Method.Name, (Type) null); info.AddValue("TestClass", (object) this.TestClass, (Type) null); info.AddValue("GenericArgumentAssemblyName", GenericArgument.Assembly.Name); info.AddValue("GenericArgumentTypeName", GenericArgument.Name); } public static Type GetType(string assemblyName, string typeName) { #if XUNIT_FRAMEWORK // This behavior is only for v2, and only done on the remote app domain side if (assemblyName.EndsWith(ExecutionHelper.SubstitutionToken, StringComparison.OrdinalIgnoreCase)) assemblyName = assemblyName.Substring(0, assemblyName.Length - ExecutionHelper.SubstitutionToken.Length + 1) + ExecutionHelper.PlatformSuffix; #endif #if NET35 || NET452 // Support both long name ("assembly, version=xxxx, etc.") and short name ("assembly") var assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName == assemblyName || a.GetName().Name == assemblyName); if (assembly == null) { try { assembly = Assembly.Load(assemblyName); } catch { } } #else System.Reflection.Assembly assembly = null; try { // Make sure we only use the short form var an = new AssemblyName(assemblyName); assembly = System.Reflection.Assembly.Load(new AssemblyName { Name = an.Name, Version = an.Version }); } catch { } #endif if (assembly == null) return null; return assembly.GetType(typeName); } public void Deserialize(IXunitSerializationInfo info) { this.TestClass = info.GetValue<ITestClass>("TestClass"); string assemblyName = info.GetValue<string>("GenericArgumentAssemblyName"); string typeName = info.GetValue<string>("GenericArgumentTypeName"); this.GenericArgument = Reflector.Wrap(GetType(assemblyName, typeName)); this.Method = this.TestClass.Class.GetMethod(info.GetValue<string>("MethodName"), true).MakeGenericMethod(GenericArgument); } } 

2) You need to write your own discoverer for common methods, it must be a subclass of IXunitTestCaseDiscoverer

 // assuming namespace Contosco public class GenericMethodDiscoverer : IXunitTestCaseDiscoverer { public GenericMethodDiscoverer(IMessageSink diagnosticMessageSink) { DiagnosticMessageSink = diagnosticMessageSink; } protected IMessageSink DiagnosticMessageSink { get; } public IEnumerable<IXunitTestCase> Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) { var result = new List<IXunitTestCase>(); var types = factAttribute.GetNamedArgument<Type[]>("Types"); foreach (var type in types) { var typeInfo = new ReflectionTypeInfo(type); var genericMethodInfo = testMethod.Method.MakeGenericMethod(typeInfo); var genericTestMethod = new GenericTestMethod(testMethod.TestClass, genericMethodInfo, typeInfo); result.Add( new XunitTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), genericTestMethod)); } return result; } } 

3) Finally, you can make your attribute for common methods and bind it to your custom discoverer with the XunitTestCaseDiscoverer attribute

 // assuming namespace Contosco [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] [XunitTestCaseDiscoverer("Contosco.GenericMethodDiscoverer", "Contosco")] public sealed class GenericMethodAttribute : FactAttribute { public Type[] Types { get; private set; } public GenericMethodAttribute(Type[] types) { Types = types; } } 

Using:

 [GenericMethod(new Type[] { typeof(double), typeof(int) })] public void TestGeneric<T>() { Assert.Equal(typeof(T), typeof(double)); } 
+3
source

All Articles