Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

xUnit theory test using generics

Tags:

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 generic T parameter as double. Is it possible to use MemberData to specify the generic type parameter for a test with a signature like:

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

If it is not possible with MemberData or any other provided attribute (which I'm suspecting that it isn't), is it possible to create an attribute for Xunit that can achieve this? Maybe something along the lines of specifying Types in the Scenarios method and using reflection in a similar manner to Jon Skeet's answer here: Generics in C#, using type of a variable as parameter

like image 951
Ayb4btu Avatar asked Sep 19 '16 10:09

Ayb4btu


People also ask

What is difference between Fact and Theory in xUnit?

Fact vs Theory Tests The primary difference between fact and theory tests in xUnit is whether the test has any parameters. Theory tests take multiple different inputs and hold true for a particular set of data, whereas a Fact is always true, and tests invariant conditions.

What is generic type of testing?

The generic test method has a different name (doesn't matter what it is as long as it's different) so that the GetMethod call doesn't need to specify the parameter types. There should be only one method by that name and it's public so it doesn't need BindingFlags , either.

What is Theory attribute in xUnit?

xUnit uses the [Fact] attribute to denote a parameterless unit test, which tests invariants in your code. In contrast, the [Theory] attribute denotes a parameterised test that is true for a subset of data. That data can be supplied in a number of ways, but the most common is with an [InlineData] attribute.

What is InlineData C#?

[InlineData] allows us to specify that a separate test is run on a particular Theory method for each instance of [InlineData] .


1 Answers

You can simply include Type as an input parameter instead. E.g.:

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

There is no need to go with generics on xunit.

Edit (if you really need generics)

1) You need to subclass ITestMethod to persist generic method info, it also has 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=x.x.x.x, 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 generic methods, it has to be 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 generic methods and hook it to your custom discoverer by 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;     } } 

Usage:

[GenericMethod(new Type[] { typeof(double), typeof(int) })] public void TestGeneric<T>() {   Assert.Equal(typeof(T), typeof(double)); } 
like image 145
Ondrej Svejdar Avatar answered Sep 22 '22 13:09

Ondrej Svejdar