简体   繁体   English

注入/模拟外部dll抽象类的静态方法

[英]Inject/Mock external dll abstract classes' static methods

I have this situation: An azure cloud service that uses an external DLL and makes API calls. 我有这种情况:一种使用外部DLL并进行API调用的Azure云服务。 This DLL has an abstract class that has a static method to return a subclass reference I need to use to make the API calls. 该DLL具有一个抽象类,该抽象类具有一个静态方法来返回我需要用来进行API调用的子类引用。

Now for testing purposes, we run the cloud service in an emulator and run our unit tests. 现在出于测试目的,我们在模拟器中运行云服务并运行我们的单元测试。 But we don't want to make that API call to the external system. 但是我们不想对外部系统进行该API调用。 We need to intercept it somehow. 我们需要以某种方式拦截它。 I have spent the better part of yesterday trying to see if I could do some dependency injection (unity) to do this but needless to say, no luck. 昨天的大部分时间里,我试图查看是否可以做一些依赖注入(统一)来这样做,但是不用说,没有运气。

The abstract class exposing a static method to get an instance of a subclass to actually make the API call is probably the most restrictive of scenarios. 暴露静态方法以获取子类的实例以实际进行API调用的抽象类可能是最严格的方案。

Below is some decompiled & cleaned up code to show the relevant pieces. 以下是一些经过反编译和清理的代码,以显示相关部分。

public abstract class EntityManager : System.Object
{

    private static object lockObject;
    private static Dictionary<System.Type, EntityManager> entityManagers;
    private bool isSingleton;

    public enum EntityManagerInstanceType : int
    {
        SingletonInstance = 0,
        NewInstance = 1,
    }

    static EntityManager() { }

    protected EntityManager() { }

    public static T GetEntityManager<T>(EntityManagerInstanceType instanceType) where T : EntityManager
    {
        T item;
        System.Type type = typeof(T);
        T t = default(T);
        lock (EntityManager.lockObject)
        {
            if (instanceType != EntityManagerInstanceType.SingletonInstance || !EntityManager.entityManagers.ContainsKey(type))
            {
                t = (T)System.Activator.CreateInstance(type, true);
                try
                {                        
                    t.isSingleton = instanceType == EntityManagerInstanceType.SingletonInstance;                        
                }
                catch (Exception adapterException)
                {                        
                    throw;
                }
                if (instanceType == EntityManagerInstanceType.SingletonInstance)
                {
                    EntityManager.entityManagers[type] = t;
                }
                return t;
            }
            else
            {
                item = (T)EntityManager.entityManagers[type];
            }
        }
        return item;
    }

    protected object ProcessRequest(string methodName, object request) { return new object(); }
}

public class PersonaEntityManager : EntityManager
{
    protected PersonaEntityManager() { }

    public PersonaResponseData UpdatePersona(PersonaUpdateRequestData requestData)
    {
        return (PersonaResponseData)base.ProcessRequest("Mdm.UpdatePersona", requestData);
    }
}

public class PublisherWorkerRole : RoleEntryPoint
{
    public bool UpdatePersona(PersonaUpdateRequestData contact, string MessageId)
    {
        PersonaEntityManager mgr = EntityManager.GetEntityManager<PersonaEntityManager>(EntityManager.EntityManagerInstanceType.NewInstance);
        var resp = mgr.UpdatePersona(contact);
        return resp != null;
    }
}

What is the ideal approach in this scenario? 在这种情况下,理想的方法是什么? Is this even testable short of setting up our own mock API and changing the application config for test to call our mock API instead? 甚至可以通过设置自己的模拟API并更改应用程序测试配置来调用模拟API来进行测试吗?

Let me know if you need me to elaborate on this further. 如果您需要我进一步详细说明,请告诉我。

One approach is to use something like ms shims or typemock to mock out the static call. 一种方法是使用ms shimstypemock之类的东西来模拟静态调用。 This would reduce the impact to your production code, but if you're not already using them may require a financial investment. 这样可以减少对生产代码的影响,但是如果您尚未使用它们,则可能需要进行财务投资。 These libraries are able to intercept calls that other mocking frameworks can't so in addition to allowing you mock static calls, they would also allow you to create mock versions of the PersonaEntityManager which you would also need. 这些库能够拦截其他模拟框架无法进行的调用,除了允许您模拟静态调用外,它们还允许您创建PersonaEntityManager模拟版本,这也是您所需要的。

As you've mentioned in your comment below, the following approach doesn't work because you need to be able to Mock the PersonaEntityManager class so that you can intercept the call to UpdatePersona , which as it's not virtual standard mocking frameworks can't do. 正如您在下面的评论中提到的那样,以下方法行不通,因为您需要能够模拟PersonaEntityManager类,以便可以拦截对UpdatePersona的调用,因为这不是虚拟标准模拟框架无法实现的。 I've left the approach below for completeness, since it is the approach I would typically use to isolate a static dependency. 为了完整起见,我离开了下面的方法,因为这是我通常用来隔离静态依赖项的方法。

If you don't mind modifying your production code is to isolate the dependency behind a wrapper class. 如果您不介意修改生产代码,则是将依赖关系隔离在包装器类后面。 This wrapper class can then be injected into your code in the normal way. 然后可以以常规方式将此包装器类注入您的代码中。

So you would end up with some wrapper code something like this: 因此,您最终会得到一些包装代码,如下所示:

public interface IEntityManagerWrapper {
    T GetEntityManager<T>(EntityManager.EntityManagerInstanceType instanceType) where T : EntityManager;
}

public class EntityManagerWrapper : IEntityManagerWrapper {
    public T GetEntityManager<T>(EntityManager.EntityManagerInstanceType instanceType) where T : EntityManager {
        return EntityManager.GetEntityManager<T>(instanceType);
    }
}

The IEntityWrapper can be setup to be injected using Unity and then mocked using your mocking framework of choice to return mock instances of the other classes you depend on like PesonaEntityManager . 可以将IEntityWrapper设置为使用Unity注入,然后使用选择的模拟框架进行模拟,以返回您依赖的其他类的模拟实例,例如PesonaEntityManager

So, your production code would look like this: 因此,您的生产代码将如下所示:

public class MyProductionCode{
    private IEntityManagerWrapper _entityManager;

    public MyProductionCode(IEntityManagerWrapper entityManager) {
        _entityManager = entityManager;
    }

    public void DoStuff() {
        PersonaEntityManager pem = _entityManager.GetEntityManager<PersonaEntityManager>(EntityManager.EntityManagerInstanceType.NewInstance);

        var response = pem.UpdatePersona(new PersonaUpdateRequestData());
    }
}

And the test code would have looked like this (assuming you're using Moq): 测试代码看起来像这样(假设您使用的是Moq):

[Test]
public void TestSomeStuff() {
    var em = new Mock<IEntityManagerWrapper>();
    var pe = new Mock<PersonaEntityManager>();
    pe.Setup(x => x.UpdatePersona(It.IsAny<PersonaUpdateRequestData>())).Returns(new PersonaResponseData());
    em.Setup(x=>x.GetEntityManager<PersonaEntityManager>(It.IsAny<EntityManager.EntityManagerInstanceType>())).Returns(pe.Object);

    var sut = new MyProductionCode(em.Object);

    sut.DoStuff();
}

The EntityWrapper class itself is pretty trivial, so I would tend to test it as an integration point, so use integration level testing to ensure it works both when it is written and if it is ever changed. EntityWrapper类本身非常琐碎,因此我倾向于将其作为集成点进行测试,因此请使用集成级别测试来确保它在编写时以及更改后都可以使用。

Hmm how about creating a proxy for that service. 嗯,如何为该服务创建代理。 Expose necessary interface through proxy and inject provider (mocked or orginal) to it. 通过代理公开必要的接口,并向其注入提供者(模拟或原始)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM