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

时间:2015-05-17 19:47:48

标签: c# unit-testing dependency-injection inversion-of-control

我遇到这种情况:使用外部DLL并进行API调用的azure云服务。这个DLL有一个抽象类,它有一个静态方法来返回我需要用来进行API调用的子类引用。

现在出于测试目的,我们在模拟器中运行云服务并运行我们的单元测试。但我们不希望对外部系统进行API调用。我们需要以某种方式拦截它。昨天我花了很多时间试图看看我是否可以做一些依赖注入(统一)这样做但不用说,没有运气。

暴露静态方法以获取子类实例以实际进行API调用的抽象类可能是最严格的场景。

下面是一些反编译的&清理代码以显示相关部分。

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

在这种情况下,理想的方法是什么?这是否可以测试是否缺乏设置我们自己的模拟API和更改测试的应用程序配置以调用我们的模拟API?

如果您需要我进一步详细说明,请告诉我。

2 个答案:

答案 0 :(得分:1)

一种方法是使用类似ms shimstypemock的方法来模拟静态调用。这样可以减少对生产代码的影响,但如果您尚未使用它们,可能需要进行财务投资。这些库能够拦截其他模拟框架无法调用的调用,除了允许您模拟静态调用之外,它们还允许您创建您还需要的PersonaEntityManager的模拟版本。 p>

正如您在下面的评论中提到的,以下方法无法正常工作,因为您需要能够模拟PersonaEntityManager类,以便您可以拦截对{{1}的调用因为它不是虚拟标准模拟框架所不能做到的。我已经完成了下面的方法,因为这是我通常用来隔离静态依赖的方法。

如果您不介意修改生产代码,请将包装类背后的依赖关系隔离开来。然后可以以正常方式将此包装类注入到代码中。

所以你最终会得到一些像这样的包装代码:

UpdatePersona

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); } } 可以设置为使用Unity注入,然后使用您选择的模拟框架进行模拟,以返回您所依赖的其他类的模拟实例,如IEntityWrapper

因此,您的生产代码如下所示:

PesonaEntityManager

测试代码看起来像这样(假设你正在使用Moq):

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

[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(); } 类本身非常简单,因此我倾向于将其作为集成点进行测试,因此请使用集成级别测试来确保它在写入时以及是否在更改时都能正常工作。

答案 1 :(得分:0)

嗯如何为该服务创建代理。通过代理公开必要的接口并向其注入提供者(模拟或原始)。