Introduction
Microsoft Fakes is a useful technology that allows to create fake stub and shim wrappers to use in testing process. However, it has some difficulties in implementing generic methods.
Problem
Assume that you have the following interface and class:
namespace GenericClassLibrary
{
public interface InterfaceWithGenericMethods
{
void NonGenericMethod();
T GenericMethod<T>();
}
public class GenericClass : InterfaceWithGenericMethods
{
public void NonGenericMethod()
{
}
public T GenericMethod<T>()
{
return default(T);
}
}
}
Another project that uses fakes assembly of this class and has a stub of interface:
namespace GenericClassLibrary.Fakes
{
[StubClass(typeof(InterfaceWithGenericMethods))]
public class StubInterfaceWithGenericMethods :
StubBase<InterfaceWithGenericMethods>, InterfaceWithGenericMethods
{
public StubInterfaceWithGenericMethods();
public FakesDelegates.Action NonGenericMethod;
public void GenericMethodOf1<T>(FakesDelegates.Func<T> stub);
}
}
Assigning a new method for a non-generic method is as easy as assigning a new delegate to NonGenericMethod
property of stub:
var stub = new StubInterfaceWithGenericMethods();
stub.NonGenericMethod = Test;
stub.NonGenericMethod = () => Test();
Unfortunately, with generic methods, it is not that easy. If you have a new fake generic methods in other classes, you can't assign it directly!
public T NewFakeGenericMethod<T>()
{
return default(T);
}
stub.GenericMethodOf1(NewFakeGenericMethod);
So you need to add stub method not for generic definition but for every implementation of generic method.
stub.GenericMethodOf1(NewFakeGenericMethod<ClassForGenericMethod>);
But what if the amount of classes you need to add is sufficient?
In my case, I needed to add generics for the whole assembly with 1000+ object types.
Inserting 1000+ lines of code didn't look good for me.
Solution
The idea is to construct the generic method and delegate at runtime and then invoke the stub method.
I have moved all code to a separate helper class, so it can easily be reused.
NOTE: This example works only for methods with single generic parameter.
However, if you want, you can adjust it for more parameters.
The complete code is listed here:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
namespace FakesLibrary
{
public class FakesHelper
{
public static void AddGenericStubMethod(object target,
string addGenericStubMethodName, string genericMethodName, Type replacementType)
{
MethodInfo addGenericStubMethod = target.GetType().GetMethod(addGenericStubMethodName);
MethodInfo genericMethod = target.GetType().GetMethod(genericMethodName);
AddGenericStubMethod(target, addGenericStubMethod, genericMethod, replacementType);
}
public static void AddGenericStubMethod(object stub,
MethodInfo addGenericStubMethod, MethodInfo genericMethod, Type objectType)
{
try
{
Type genericDelegateParameterType = addGenericStubMethod.GetParameters()[0].ParameterType;
Type[] genericTypeArgs = genericDelegateParameterType.GetGenericArguments();
for (int i = 0; i < genericTypeArgs.Length; i++)
if (genericTypeArgs[i].IsGenericParameter)
genericTypeArgs[i] = objectType;
else if (genericTypeArgs[i].IsGenericType) genericTypeArgs[i] = genericTypeArgs[i].GetGenericTypeDefinition().MakeGenericType(objectType);
Type typeDelegateGenericDefinition = genericDelegateParameterType.GetGenericTypeDefinition();
Type typeOfDelegate = typeDelegateGenericDefinition.MakeGenericType(genericTypeArgs);
MethodInfo genericMethodOfType = genericMethod.MakeGenericMethod(objectType);
Delegate delegateOfType = Delegate.CreateDelegate(typeOfDelegate, stub, genericMethodOfType);
MethodInfo addStubMethodOfType = addGenericStubMethod.MakeGenericMethod(objectType);
addStubMethodOfType.Invoke(stub, new object[] { delegateOfType });
}
catch
{
}
}
public static void AddGenericStubMethodsForAssemblyTypes(object stub, Assembly assembly,
string addGenericStubMethodName, string genericMethodName, Type parentType = null)
{
MethodInfo addGenericStubMethod = stub.GetType().GetMethod(addGenericStubMethodName);
MethodInfo genericMethod = stub.GetType().GetMethod(genericMethodName);
AddGenericStubMethodsForAssemblyTypes(stub, assembly, addGenericStubMethod, genericMethod, parentType);
}
public static void AddGenericStubMethodsForAssemblyTypes(object stub, Assembly assembly,
MethodInfo addGenericStubMethod, MethodInfo genericMethod, Type parentType = null)
{
foreach (Type type in assembly.GetTypes())
{
if (parentType == null || parentType.IsAssignableFrom(type))
{
AddGenericStubMethod(stub, addGenericStubMethod, genericMethod, type);
}
}
}
}
}
Example code for the case described in the problem:
public class MyStubInterfaceWithGenericMethods : StubInterfaceWithGenericMethods
{
public T NewFakeGenericMethod<T>()
{
return Activator.CreateInstance<T>();
}
public MyStubInterfaceWithGenericMethods()
: base()
{
var assembly = typeof(ClassForGenericMethod).Assembly;
FakesHelper.AddGenericStubMethodsForAssemblyTypes
(this, assembly, "GenericMethodOf1", "NewFakeGenericMethod");
}
}
public class FakesTest
{
[STAThread]
public static void Main(string[] args)
{
var stub = new MyStubInterfaceWithGenericMethods() as InterfaceWithGenericMethods;
var test = stub.GenericMethod<ClassForGenericMethod>();
}
}