用Moq在现有实例周围创建Mock? [英] Creating Mock with Moq around existing instance?

查看:65
本文介绍了用Moq在现有实例周围创建Mock?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

比方说,我们已经进行了具有广泛配置IConfiguration的集成测试.我已经将测试设置为可与autofac容器一起使用,现在我想使用Mock替换其属性之一上的操作,而无需模拟或替换其他所有内容:

Let's say we have integration test with extensive configuration IConfiguration. I've setup the test to work with autofac containers, and now I'd like to use Mock to replace the operation on one of it's properties without the need to mock or replace everything else:

var config = MyTestContainer.Resolve<IConfiguration>();
//let's say that config.UseFeatureX = false;

//here, I'd like to create mock "around" the existing instance:
var mockedConfig = Mock.CreateWith(config);  //CreateWith => a method I'd like to find how to do
mockedConfig.Setup(c => c.UseFeatureX).Returns(true);

如何围绕现有实例进行包装?它应该与.CallBase类似,但我希望能有一种方法来调用基本值,而不仅仅是调用基本实现.

How to do this wrapping around existing instance? It should be similar to .CallBase but instead of just calling base implementation, I was hoping there would be a way to call base values.

推荐答案

我相信默认情况下Moq允许您传递用于IConfiguration实现的构造函数参数,它将为您创建该类的新实例.如果我正确理解了您的问题,那么您宁愿使用一个预先构建的实例. 我假设您知道 CallBase 并不能完全满足您的需求.

I believe by default Moq allows you to pass constructor parameters for IConfiguration implementation and it will make a new instance of that class for you for you. If I understand your problem correctly you want to rather use a pre-constructed instance. I assume you are aware of CallBase and it does not quite do what you need.

因此,基本上,以下代码段说明了该问题:

So basically, the following snippet illustrates the issue:

//suppose we've got a class:
public class A
{
    public string Test {get;set;}
    public virtual string ReturnTest() => Test;
}
//and some code below:
void Main()
{
    var config = new A() {
        Test = "TEST"
    } ;

    var mockedConfig = new Mock<A>(); // first we run a stock standard mock
    mockedConfig.CallBase = true; // we will enable CallBase just to point out that it makes no difference  
    var o = mockedConfig.Object;
    Console.WriteLine(o.ReturnTest()); // this will be null because Test has not been initialised from constructor
    mockedConfig.Setup(c => c.ReturnTest()).Returns("mocked"); // of course if you set up your mocks - you will get the value
    Console.WriteLine(o.ReturnTest()); // this will be "mocked" now, no surprises
}

现在,知道Moq在内部利用了 Castle DynamicProxy ,它实际上使我们可以为实例生成代理(它们称为

now, knowing that Moq internally leverages Castle DynamicProxy and it actually allows us to generate proxies for instances (they call it Class proxy with target). Therefore the question is - how do we get Moq to make one for us. It seems there's no such option out of the box, and simply injecting the override didn't quite go well as there's not much inversion of control inside the library and most of the types and properties are marked as internal, making inheritance virtually impossible.

Castle Proxy被更多的用户所迷惑,并且有很多公开的方法可供覆盖.因此,让我们定义一个ProxyGenerator类,该类将接受方法Moq调用并向其中添加所需的功能(只需比较

Castle Proxy is however much more user firendly and has quite a few methods exposed and available for overriding. So let us define a ProxyGenerator class that would take the method Moq calls and add required functionality to it (just compare CreateClassProxyWithTarget and CreateClassProxy implementations - they are almost identical!)

class MyProxyGenerator : ProxyGenerator
{
    object _target;

    public MyProxyGenerator(object target) {
        _target = target; // this is the missing piece, we'll have to pass it on to Castle proxy
    }
    // this method is 90% taken from the library source. I only had to tweak two lines (see below)
    public override object CreateClassProxy(Type classToProxy, Type[] additionalInterfacesToProxy, ProxyGenerationOptions options, object[] constructorArguments, params IInterceptor[] interceptors)
    {
        if (classToProxy == null)
        {
            throw new ArgumentNullException("classToProxy");
        }
        if (options == null)
        {
            throw new ArgumentNullException("options");
        }
        if (!classToProxy.GetTypeInfo().IsClass)
        {
            throw new ArgumentException("'classToProxy' must be a class", "classToProxy");
        }
        CheckNotGenericTypeDefinition(classToProxy, "classToProxy");
        CheckNotGenericTypeDefinitions(additionalInterfacesToProxy, "additionalInterfacesToProxy");
        Type proxyType = CreateClassProxyTypeWithTarget(classToProxy, additionalInterfacesToProxy, options); // these really are the two lines that matter
        List<object> list =  BuildArgumentListForClassProxyWithTarget(_target, options, interceptors);       // these really are the two lines that matter
        if (constructorArguments != null && constructorArguments.Length != 0)
        {
            list.AddRange(constructorArguments);
        }
        return CreateClassProxyInstance(proxyType, list, classToProxy, constructorArguments);
    }
}

如果以上所有内容都相对简单明了,那么将其实际输入到Moq中将是一个小技巧.正如我提到的,大多数结构都标记为internal,因此我们必须使用反射来实现:

if all of the above was relativaly straightforward, actually feeding it into Moq is going to be somewhat of a hack. As I mentioned, most of the structures are marked internal so we'll have to use reflection to get through:

public class MyMock<T> : Mock<T>, IDisposable where T : class
{
    void PopulateFactoryReferences()
    {
        // Moq tries ridiculously hard to protect their internal structures - pretty much every class that could be of interest to us is marked internal
        // All below code is basically serving one simple purpose = to swap a `ProxyGenerator` field on the `ProxyFactory.Instance` singleton
        // all types are internal so reflection it is
        // I will invite you to make this a bit cleaner by obtaining the `_generatorFieldInfo` value once and caching it for later
        var moqAssembly = Assembly.Load(nameof(Moq));
        var proxyFactoryType = moqAssembly.GetType("Moq.ProxyFactory");
        var castleProxyFactoryType = moqAssembly.GetType("Moq.CastleProxyFactory");     
        var proxyFactoryInstanceProperty = proxyFactoryType.GetProperty("Instance");
        _generatorFieldInfo = castleProxyFactoryType.GetField("generator", BindingFlags.NonPublic | BindingFlags.Instance);     
        _castleProxyFactoryInstance = proxyFactoryInstanceProperty.GetValue(null);
        _originalProxyFactory = _generatorFieldInfo.GetValue(_castleProxyFactoryInstance);//save default value to restore it later
    }

    public MyMock(T targetInstance) {       
        PopulateFactoryReferences();
        // this is where we do the trick!
        _generatorFieldInfo.SetValue(_castleProxyFactoryInstance, new MyProxyGenerator(targetInstance));
    }

    private FieldInfo _generatorFieldInfo;
    private object _castleProxyFactoryInstance;
    private object _originalProxyFactory;

    public void Dispose()
    {
         // you will notice I opted to implement IDisposable here. 
         // My goal is to ensure I restore the original value on Moq's internal static class property in case you will want to mix up this class with stock standard implementation
         // there are probably other ways to ensure reference is restored reliably, but I'll leave that as another challenge for you to tackle
        _generatorFieldInfo.SetValue(_castleProxyFactoryInstance, _originalProxyFactory);
    }
}

鉴于我们已经完成了上述工作,实际的解决方案将如下所示:

given we've got the above working, the actual solution would look like so:

    var config = new A()
    {
        Test = "TEST"
    };
    using (var superMock = new MyMock<A>(config)) // now we can pass instances!
    {
        superMock.CallBase = true; // you still need this, because as far as Moq is oncerned it passes control over to CastleDynamicProxy   
        var o1 = superMock.Object;
        Console.WriteLine(o1.ReturnTest()); // but this should return TEST
    }

希望这会有所帮助.

这篇关于用Moq在现有实例周围创建Mock?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆