Ninject factory extensions with async await - c#

If I use a a factory interface such as this
public interface IBoredFactory
{
IBored Create(<some args>)
}
and in the binding do this
kernel.Bind<IBoredFactory>().ToFactory();
kernel.Bind<IBored>().To<Yawn>();
it works just as expected, and the code returns a Yawn
But if I change the Factory interface to this
public interface IBoredFactory
{
Task<IBored> Create(<some args>)
}
The factory proxy gets built, but the thread never returns from the Create(), the caller endlessly awaits.
Am I missing something here ?

As #Andreas Appelros suggests Ninject is returning an unfinished Task<T>.
As there's actually no async ninject API it does not really make sense to use async-await for resolving instances, even if there'd be something async involved in the creation, this would first need to be converted to a synchronous-call to satisfy ninject's binding API's ("sync over async") - which is not really a good thing to do. Then do convert it it to async again ("async over sync") doesn't make any more sense, either, as it's just adding overhead.
However, if for some reason you still want to do it, you'll have to use a custom IInstanceProvider, for example the following will work (code can be improved, though):
public class AsyncInstanceProvider : StandardInstanceProvider
{
public override object GetInstance(
IInstanceResolver instanceResolver, MethodInfo methodInfo, object[] arguments)
{
object result = base.GetInstance(
instanceResolver, methodInfo, arguments);
if (ReturnsTaskResult(methodInfo))
{
return CreateFinishedTask(methodInfo, result);
}
return result;
}
protected override Type GetType(MethodInfo methodInfo, object[] arguments)
{
if (ReturnsTaskResult(methodInfo))
{
return methodInfo.ReturnType.GenericTypeArguments[0];
}
return base.GetType(methodInfo, arguments);
}
private bool ReturnsTaskResult(MethodInfo methodInfo)
{
return (methodInfo.ReturnType.IsGenericType
&& methodInfo.ReturnType.GetGenericTypeDefinition() == typeof(Task<>));
}
private object CreateFinishedTask(MethodInfo methodInfo, object result)
{
var openGenericMethod = typeof(Task).GetMethod("FromResult");
var closedGenericMethod = openGenericMethod.MakeGenericMethod(
methodInfo.ReturnType.GenericTypeArguments[0]);
return closedGenericMethod.Invoke(null, new object[1] { result });
}
}
Globally Applying the new IInstanceProvider
If you want to use that Instance Provider globally, perform the following rebind after loading the Factory extension:
Rebind<IInstanceProvider>().To<AsyncInstanceProvider>();
Complete Test-Code
And, for reference, the complete code including a sync and an async test (requires xunit and FluentAssertion nuget packages):
namespace NinjectTest.AsyncFactory_44983826
{
using System;
using System.Reflection;
using System.Threading.Tasks;
using Ninject;
using Ninject.Extensions.Factory;
using Ninject.Extensions.Factory.Factory;
using FluentAssertions;
using Xunit;
public class AsyncInstanceProvider : StandardInstanceProvider
{
public override object GetInstance(IInstanceResolver instanceResolver, MethodInfo methodInfo, object[] arguments)
{
object result = base.GetInstance(instanceResolver, methodInfo, arguments);
if (ReturnsTaskResult(methodInfo))
{
return CreateFinishedTask(methodInfo, result);
}
return result;
}
protected override Type GetType(MethodInfo methodInfo, object[] arguments)
{
if (ReturnsTaskResult(methodInfo))
{
return methodInfo.ReturnType.GenericTypeArguments[0];
}
return base.GetType(methodInfo, arguments);
}
private bool ReturnsTaskResult(MethodInfo methodInfo)
{
return (methodInfo.ReturnType.IsGenericType && methodInfo.ReturnType.GetGenericTypeDefinition() == typeof(Task<>));
}
private object CreateFinishedTask(MethodInfo methodInfo, object result)
{
var openGenericMethod = typeof(Task).GetMethod("FromResult");
var closedGenericMethod = openGenericMethod.MakeGenericMethod(methodInfo.ReturnType.GenericTypeArguments[0]);
return closedGenericMethod.Invoke(null, new object[1] { result });
}
}
public class Foo
{
public string Id { get; private set; }
public Bar Bar { get; private set; }
public Foo(string id, Bar bar)
{
this.Id = id;
this.Bar = bar;
}
}
public class Bar { }
public interface IFactory
{
Foo Create(string id);
Task<Foo> CreateAsync(string id);
}
public class Test
{
[Fact]
public void SyncTest()
{
var kernel = new StandardKernel();
kernel
.Bind<IFactory>()
.ToFactory(() => new AsyncInstanceProvider());
const string expectedId = "Hello You!";
kernel.Get<IFactory>().Create(expectedId)
.Id.Should().Be(expectedId);
}
[Fact]
public async Task AsyncTest()
{
var kernel = new StandardKernel();
kernel
.Bind<IFactory>()
.ToFactory(() => new AsyncInstanceProvider());
const string expectedId = "Hello You!";
Foo result = await kernel.Get<IFactory>().CreateAsync(expectedId);
result.Id.Should().Be(expectedId);
}
}
}

Related

Automatically set `TInput` equal to `TOutput` for the fluent syntax

I am building my personal automation framework with fluent syntax. Here is a pipeline example
var pipeline = Core.Runner.CreatePipeline()
.BeginMany(Sources.Csv(#"..."))
.ThenTransform<dynamic,string[]>(record=> record.id.Split(":"))
.ThenTransform<string[], (string, string)>(record => (record[0], record[1]))
.ThenTransform<(string, string), dynamic>(new {...})
I wonder is there any way to improve usability and automatically set TInput equal to TOutput for the next ThenTransform<TInput, TOutput> in the chain and validate types on build?
Desired outcome
var pipeline = Core.Runner.CreatePipeline()
.BeginMany(Sources.Csv(#"..."))
.ThenTransform<string[]>(record=> record.id.Split(":")) // TInput is dynamic, TOutput is string[]
.ThenTransform<(string, string)>(record => (record[0], record[1])) // TInput is string[], TOuput is (string,string)
.ThenTransform<dynamic>(new {...}) // etc
an even better outcome which might be possible because lambda knows return type
var pipeline = Core.Runner.CreatePipeline()
.BeginMany(Sources.Csv(#"..."))
.ThenTransform(record=> record.id.Split(":")) // TInput is dynamic, TOutput is string[]
.ThenTransform(record => (record[0], record[1])) // TInput is string[], TOuput is (string,string)
.ThenTransform(new {...}) // etc
You have not specified how you are storing state here, but for the generics you can do something like this:
using System;
namespace ConsoleApp16
{
class Program
{
static void Main(string[] args)
{
var pipeline = Core.Runner.CreatePipeline<dynamic>()
.BeginMany(Sources.Csv(#"..."))
// Type cannot be inferred from dynamic
.ThenTransform<string[]>(record => record.id.Split(":"))
.ThenTransform(record => (record[0], record[1]))
.ThenTransform(s => s.Item1);
}
}
internal class Sources
{
public static object Csv(string s)
{
return new object();
}
}
internal class Core
{
public class Runner
{
public static Pipeline<TInput> CreatePipeline<TInput>()
{
return new Pipeline<TInput>(new PipelineState());
}
}
}
internal class PipelineState
{
public bool MyState { get; set; }
}
internal class Pipeline<TInput>
{
private readonly PipelineState _pipelineState;
public Pipeline(PipelineState pipelineState)
{
_pipelineState = pipelineState;
}
public Pipeline<TInput> BeginMany(object csv)
{
// Update state
return this;
}
public Pipeline<TOutput> ThenTransform<TOutput>(Func<TInput, TOutput> func)
{
// Update state
return new Pipeline<TOutput>(_pipelineState);
}
}
}
You can probably improve upon this by having different PipelineBuilder classes that are returned by different methods. For instance BeginMany might return a class that has the ThenTransform method so that the order is enforced:
using System;
namespace ConsoleApp16
{
class Program
{
static void Main(string[] args)
{
var pipeline = Core.Runner.CreatePipeline()
.BeginMany(Sources.Csv(#"..."))
// Type cannot be inferred from dynamic
.ThenTransform<string[]>(record => record.id.Split(":"))
.ThenTransform(record => (record[0], record[1]))
.ThenTransform(s => s.Item1)
.Build();
}
}
internal class Sources
{
public static Source<dynamic> Csv(string s)
{
return new Source<dynamic>();
}
}
internal class Source<T>
{
}
internal class Core
{
public class Runner
{
public static PipelineBuilder CreatePipeline()
{
return new PipelineBuilder(new PipelineState());
}
}
}
internal class PipelineState
{
public bool MyState { get; set; }
}
internal class PipelineBuilder
{
protected readonly PipelineState State;
public PipelineBuilder(PipelineState state)
{
State = state;
}
public PipelineBuilder<TInput> BeginMany<TInput>(Source<TInput> source)
{
// Update state
return new PipelineBuilder<TInput>(State);
}
public Pipeline Build()
{
// Populate from state
return new Pipeline();
}
}
internal class PipelineBuilder<TInput> : PipelineBuilder
{
public PipelineBuilder(PipelineState pipelineState) : base(pipelineState)
{
}
public PipelineBuilder<TOutput> ThenTransform<TOutput>(Func<TInput, TOutput> func)
{
// Update state
return new PipelineBuilder<TOutput>(State);
}
}
internal class Pipeline
{
}
}
It's worth looking into the builder pattern, when combined with interfaces and extension methods it can be pretty powerful. One great example is Microsoft.Extensions.Configuration https://github.com/dotnet/extensions/tree/release/3.1/src/Configuration

Pass generic type to Theory [duplicate]

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
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));
}

How to pass a Func<> property/method to a NancyFx Get() method?

The NancyFx (2.x) NancyModule.Get() method is defined as:
public virtual void Get(string path, Func<dynamic, object> action, [Func<NancyContext, bool> condition = null], [string name = null]);
The normal usage is:
public class MyModule
{
public MyModule()
{
this.Get("/", parameters => {
this.RequestHandler = new RequestHandler();
return this.RequestHandler.HandleRequest("/", parameters, someOtherInfo);
});
}
}
I want to define the second parameter as a property, so that I can use for several paths like this:
public class MyModule
{
Func<dynamic, object> indexHandler = parameters => {
// Error: Keyword "this" is not available in this context
this.RequestHandler = new RequestHandler();
// Error: Keyword "this" is not available in this context
return this.RequestHandler.HandleRequest("/", parameters, someOtherInfo);
};
public MyModule()
{
this.Get("/", indexHandler);
this.Get("/index", indexHandler);
}
}
If I do this it works:
public class MyModule
{
public MyModule()
{
Func<dynamic, object> indexHandler = parameters => {
this.RequestHandler = new RequestHandler();
return this.RequestHandler.HandleRequest("/", parameters, someOtherInfo);
};
this.Get("/", indexHandler);
this.Get("/index", indexHandler);
}
}
But I don't want to define it in the constructor. What am I doing wrong? Is there any other way to do this?
MVCE
Dependancy Package: Nancy (Version: 2.0.0-clinteastwood)
using Nancy;
using Nancy.Responses.Negotiation;
namespace MyNamespace
{
public class MyModule : NancyModule
{
private RequestHandler RequestHandler;
private object IndexHandler(dynamic parameters)
{
this.RequestHandler = new RequestHandler();
var someOtherInfo = "";
return this.RequestHandler.HandleRequest("/", parameters, someOtherInfo);
}
public MyModule()
{
this.Get("/", IndexHandler);
this.Get("/index", IndexHandler);
this.Get("/home", parameters => {
return this.Negotiate.WithView("myview.html");
});
}
}
public class RequestHandler
{
public Negotiator HandleRequest(string path, dynamic parameters, string someOtherInfo)
{
return new Negotiator(new NancyContext());
}
}
}
This should work:
public class MyModule
{
public MyModule()
{
this.Get("/", IndexHandler);
this.Get("/index", IndexHandler);
}
private object IndexHandler(dynamic parameters) {
this.RequestHandler = new RequestHandler();
return this.RequestHandler.HandleRequest("/", parameters, someOtherInfo);
}
}
Andrew's answer is valid and should've been sufficient but apparently (in your MVCE) that method definition doesn't exist.
Here's the correct definition (at least the one VS wants):
public virtual void Get(string path, Func<object, Task<object>> action, Func<NancyContext, bool> condition = null, string name = null)
Luckly your HandleRequest is awaitable so you only need to edit the return type.
Here's the correct definition:
private Task<object> IndexHandler(dynamic parameters)
{
this.RequestHandler = new RequestHandler();
var someOtherInfo = "";
return this.RequestHandler.HandleRequest("/", parameters, someOtherInfo);
}
Hope this helps!

Iterate a list of types and use it as a generic parameter [duplicate]

public interface IBar {
}
public class Bar : IBar {
}
public class Bar2 : IBar {
}
public interface IFoo {
Task<T> Get<T>(T o) where T : IBar;
}
public class Foo : IFoo {
public async Task<T> Get<T>(T o) where T : IBar {
...
}
}
I can then call this method using reflection:
var method = typeof(IFoo).GetMethod(nameof(IFoo.Get));
var generic = method.MakeGenericMethod(bar2.GetType());
var task = generic.Invoke(foo, new [] { bar2 });
How do I await on this Task? and How do I cast it to Task<bar2.GetType()>?
Because Task<T> derives from Task you can await on just that, once the task is awaited you can use reflection to safely access the .Result property via reflection.
Once you have the result you will either need to store it in a IBar and use the methods and properties on that or cast to the specific type after testing to use the type specific methods.
Here is a full MCVE of it
using System;
using System.Reflection;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Test().Wait();
Console.ReadLine();
}
static async Task Test()
{
var foo = new Foo();
var bar2 = new Bar2();
object resultObject = await CallGetByReflection(foo, bar2);
IBar result = (IBar)resultObject;
result.WriteOut();
//or
if (resultObject is Bar)
{
((Bar)resultObject).Something();
}
else if (resultObject is Bar2)
{
((Bar2)resultObject).SomethingElse();
}
}
private static async Task<object> CallGetByReflection(IFoo foo, IBar bar)
{
var method = typeof(IFoo).GetMethod(nameof(IFoo.Get));
var generic = method.MakeGenericMethod(bar.GetType());
var task = (Task) generic.Invoke(foo, new[] {bar});
await task.ConfigureAwait(false);
var resultProperty = task.GetType().GetProperty("Result");
return resultProperty.GetValue(task);
}
public interface IBar
{
void WriteOut();
}
public class Bar : IBar
{
public void Something()
{
Console.WriteLine("Something");
}
public void WriteOut()
{
Console.WriteLine(nameof(Bar));
}
}
public class Bar2 : IBar
{
public void SomethingElse()
{
Console.WriteLine("SomethingElse");
}
public void WriteOut()
{
Console.WriteLine(nameof(Bar2));
}
}
public interface IFoo
{
Task<T> Get<T>(T o) where T : IBar;
}
public class Foo : IFoo
{
public async Task<T> Get<T>(T o) where T : IBar
{
await Task.Delay(100);
return o;
}
}
}
}
UPDATE: Here is a extension method to simplify the process
public static class ExtensionMethods
{
public static async Task<object> InvokeAsync(this MethodInfo #this, object obj, params object[] parameters)
{
var task = (Task)#this.Invoke(obj, parameters);
await task.ConfigureAwait(false);
var resultProperty = task.GetType().GetProperty("Result");
return resultProperty.GetValue(task);
}
}
This turns CallGetByReflection in to
private static Task<object> CallGetByReflection(IFoo foo, IBar bar)
{
var method = typeof(IFoo).GetMethod(nameof(IFoo.Get));
var generic = method.MakeGenericMethod(bar.GetType());
return generic.InvokeAsync(foo, new[] {bar});
}
UPDATE 2: Here is a new extension method that works with any awaitable type instead of only tasks by using dynamic and GetAwaiter()
public static class ExtensionMethods
{
public static async Task<object> InvokeAsync(this MethodInfo #this, object obj, params object[] parameters)
{
dynamic awaitable = #this.Invoke(obj, parameters);
await awaitable;
return awaitable.GetAwaiter().GetResult();
}
}
Based on your example you know type of returned object at compile time -> IFoo, so you can use normal casting (IFoo)
var method = typeof(IFoo).GetMethod(nameof(IFoo.Get));
var generic = method.MakeGenericMethod(typeof(IBar));
var task = (Task<IBar>)generic.Invoke(foo, new [] { bar2 });
IBar result = await task;
If you don't know a type at compile time, then use dynamic keyword
var method = typeof(IFoo).GetMethod(nameof(IFoo.Get));
var generic = method.MakeGenericMethod(bar2.GetType());
dynamic task = generic.Invoke(foo, new [] { bar2 });
IBar result = await task;
But if type of the task not a Task<iFoo> at runtime - exception will be thrown
And if you need concrete type of IBar then
var concreteResult = Convert.ChangeType(result, bar2.GetType());
On top of #ScottChamberlain answer (which is great 😉) I'll suggest a small improvement on InvokeAsync method to return Task<T> rather than Task<object>. Besides that it would be useful having a second method returning Task, which isn't supported by InvokeAsync.
using System.Threading.Tasks;
namespace System.Reflection
{
public static class MethodInfoExtensions
{
public static async Task<T> InvokeAsync<T>(this MethodInfo methodInfo, object obj, params object[] parameters)
{
dynamic awaitable = methodInfo.Invoke(obj, parameters);
await awaitable;
return (T)awaitable.GetAwaiter().GetResult();
}
public static async Task InvokeAsync(this MethodInfo methodInfo, object obj, params object[] parameters)
{
dynamic awaitable = methodInfo.Invoke(obj, parameters);
await awaitable;
}
}
}
Here is an easy and simple sample code
object[] param = {null};
var method = await (Task<bool>)typeof(YourClassName).GetMethod("YourMethodName",System.Reflection.BidingFlags.NonPublic | System.Reflection.BindingFlags.Instance).Invoke(InstanceOfYourClass, param);
you can use "result" property to avoid "await" keyword and should not decalre "async" in method.
var method = typeof(IFoo).GetMethod(nameof(IFoo.Get));
var generic = method.MakeGenericMethod(typeof(IBar));
var task = (Task<IBar>)generic.Invoke(foo, new [] { bar2 });
var resultProperty = task.GetProperty("Result");
var result = resultProperty.GetValue(task);
var convertedResult = Convert.ChangeType(result, bar2.GetType());

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 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
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));
}

Categories

Resources