15

扩展方法不适合测试(此处描述:Mocking Extension Methods with Moqhttp ://www.clariusconsulting.net/blogs/kzu/archive/2009/12/22/Howtomockextensionmethods.aspx )。

但是可能有一些模拟 Unity 方法的解决方案?就我而言,我有以下功能:

public class MyManager
{
    public MyManager(IUnityContainer container) : base(container) { }

    public IResult DoJob(IData data)
    {
        IMyLog log = MyContainer.Resolve<IMyLog>();

        ... use log.Id ...

        MyContainer.Resolve<...>();//usage for other purposes...
    }

我想确定“DoJob”方法将始终从容器中获取“IMyLog”对象,而不是从其他来源......我该如何测试呢?

我最初的想法是改变'DoJob'方法的实现和使用:

IMyLog log = UnityContainer.Resolve(typeof(IMyLog)) as IMyLog;

但是 'Resolve(Type t, ...)' 也是一种扩展方法...

欢迎任何想法。

PS 请注意,“我的日志”对象是在远离 MyManager.DoJob 的地方创建的...

4

4 回答 4

10

我知道我迟到了,但我遇到了同样的问题,并通过模拟以下方法解决了它 -

IUnityContainer.Resolve(Type t, string name, params ResolverOverride[] resolverOverrides);

例如 -

unityMock = new Mock<IUnityContainer>(MockBehavior.Strict);
validatorMock = new Mock<IValidator>(MockBehavior.Strict);
unityMock.Setup(p => p.Resolve(typeof(IValidator), null))
     .Returns(validatorMock.Object);

以防万一有人需要对此进行模拟并且无法删除对容器的依赖。

于 2015-01-23T10:32:22.620 回答
6

删除对 IUnityContainer 的依赖,事情变得更容易和更干净。相反,让统一注入您的依赖项,这些依赖项被抽象为接口。这些很容易被嘲笑。这是一个使用 Unity 的小技巧为 IMyLog 注入自动工厂的示例。

public class MyManager
{
   private readonly Func<IMyLog> logFactory;

   public MyManager(Func<IMyLog> logFactory) 
   {
       this.logFactory = logFactory;
   }

   public IResult DoJob(IData data)
   {
       IMyLog log = logFactory();

       ...
   }
}

或者,如果您不需要每次都创建实例:

public class MyManager
{
   private readonly IMyLog myLog;

   public MyManager(IMyLog myLog) 
   {
       this.myLog = myLog;
   }

   public IResult DoJob(IData data)
   {
       ...
   }
}
于 2010-09-24T22:40:52.820 回答
4

猜猜,我找到了最合适的测试解决方案:没有必要模拟统一容器并检查是否从中获取了“日志”对象。我将为“日志”对象制作一个模拟,在容器中注册其对象实例并检查该日志对象是否真的被使用。

这将完成所需的操作。

        Mock<IMyLog> mockLog = new Mock<IMyLog>();
        mockLog.Setup(mock=>mock.Id).Returns(TestLogId);

        IUnityContainer container = new UnityContainer();
        container
            .RegisterInstance(mockCommandExecutionLog.Object)
            ...
            ;

        ...

        mockLog.Verify(
            mock => mock.Id,
            Times.Once(),
            "It seems like 'Log' object is not used"
            );

谢谢。

于 2010-09-24T23:10:22.523 回答
1

我将不得不不同意这两个答案。TheCodeKing,直接使用IoC接口是完全合法的。IUnityContainer一个例子可能是 ASP.NET 项目中的控制器工厂——其中一个可能在接口上使用多种方法进行非平凡的解析。

嗯...使用自动工厂注入 labdas,无需直接对 IoC 接口进行单元测试。您可以只传递一个 lambda 并验证它是否使用正确的参数被调用。

Budda,你永远不应该在你的单元测试中引入 IoC 容器。必须手动注入依赖项。

以下是我使用的解决方案。我基本上创建了一个抽象层IUnityContainer并实现了一个简单的类,它委托给IUnityContainer. 因为我的接口不包含扩展方法,所以我可以轻松地模拟它。

public interface IDIContainer {
    void RegisterType<TFrom>() where TFrom : class;
    void RegisterType<TFrom, TTo>() where TTo : TFrom;
    void RegisterType<TFrom, TTo>(string name) where TTo : TFrom;
    void RegisterType(Type from, Type to);
    void RegisterType(Type from, Type to, string name);

    void RegisterInstance<TFrom>(TFrom instance) where TFrom : class;

    T Resolve<T>();
    T Resolve<T>(string name);
    IEnumerable<T> ResolveAll<T>();

    bool IsRegistered<TFrom>(string name) where TFrom : class;
    bool IsRegistered<TFrom>() where TFrom : class;
}


public class DIContainer : IDIContainer {
    IUnityContainer m_Container = new UnityContainer();

    #region IDIContainer Members

    public void RegisterType<TFrom>() where TFrom : class {
        m_Container.RegisterType<TFrom>();
    }

    public void RegisterType<TFrom, TTo>() where TTo : TFrom {
        m_Container.RegisterType<TFrom, TTo>();
    }

    public void RegisterType<TFrom, TTo>(string name) where TTo : TFrom {
        m_Container.RegisterType<TFrom, TTo>(name);
    }

    public void RegisterType(Type from, Type to) {
        m_Container.RegisterType(from, to);
    }

    public void RegisterType(Type from, Type to, string name) {
        m_Container.RegisterType(from, to, name);
    }

    public void RegisterInstance<TFrom>(TFrom instance) where TFrom : class {
        m_Container.RegisterInstance<TFrom>(instance);
    }

    public T Resolve<T>() {
        return m_Container.Resolve<T>();
    }

    public IEnumerable<T> ResolveAll<T>() {
        return m_Container.ResolveAll<T>();
    }

    public T Resolve<T>(string name) {
        return m_Container.Resolve<T>(name);
    }

    public bool IsRegistered<TFrom>(string name) where TFrom : class {
        return m_Container.IsRegistered<TFrom>(name);
    }

    public bool IsRegistered<TFrom>() where TFrom : class {
        return m_Container.IsRegistered<TFrom>();
    }

    #endregion
}

现在,重写你的类以使用IDIContainer

public class MyManager
{
    public MyManager(IDIContainer container) : base(container) { }

    public IResult DoJob(IData data)
    {
        IMyLog log = MyContainer.Resolve<IMyLog>();

        ... use log.Id ...

        MyContainer.Resolve<...>();//usage for other purposes...
    }
}

并像这样重写单元测试:

[TestClass]
public class Test {
  [TestMethod]
  public void TestDoJob() {
    Mock<IMyLog> mockLog = new Mock<IMyLog>();
    Mock<IDIContainer> containerMock = new Mock<IDIContainer>();

    //Setup mock container to return a log mock we set up earlier
    containerMock.Setup(c=>c.Resolve<IMyLog>()),Returns(mockLog);
    //Verify that all setups have been performed
    containerMock.VerifyAll();
  }
}
于 2010-09-27T00:14:55.477 回答