Click here to Skip to main content
15,887,214 members
Articles / Programming Languages / C#
Tip/Trick

Generic Methods Implementation in Microsoft Fakes

Rate me:
Please Sign up or sign in to vote.
4.50/5 (4 votes)
19 Oct 2014CPOL1 min read 15K   6  
Describes how to implement generic methods for Microsoft Fakes Stubs and Shims using reflection constructed delegates at runtime

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:

C#
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:

C#
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:

C#
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!

C#
public T NewFakeGenericMethod<T>()
{
    return default(T);
}

stub.GenericMethodOf1(NewFakeGenericMethod); // causes compilation error!

So you need to add stub method not for generic definition but for every implementation of generic method.

C#
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:

C#
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) // for types like List<T>
                        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
            {
                // oops... something went wrong, continue to do stuff for other assembly types in loop
            }
        }

        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:

C#
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>();
    }
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Quipu GmbH
Ukraine Ukraine
My primary job is in development and maintainance of CutomWare.NET Core Banking application which was designed for use in ProCredit Holding.

This is a large, flexible and constantly improving system which includes a huge set of modules for Front and Back-end, payment systems, loans, deposits and whatever other the Bank may need.

Application is contributed with development of several Quipu GmbH regional offices in Kiev, Moscow, Skopje, Frankfurt, San Salvador, Bogota and Accra.

Comments and Discussions

 
-- There are no messages in this forum --