如何模拟(使用Moq)Unity方法

时间:2010-09-24 22:36:49

标签: .net unit-testing extension-methods moq mocking

扩展方法不适合测试(在此处描述: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,...)'也是一种扩展方法......

欢迎任何想法。

P.S。请注意,“我的日志”对象是在远离MyManager.DoJob ...

的位置创建的

4 个答案:

答案 0 :(得分:7)

我知道我迟到了,但我遇到了同样的问题,并通过嘲笑以下方法解决了这个问题 -

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

以防万一有人需要嘲笑这个并且无法移除对容器的依赖。

答案 1 :(得分: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)
   {
       ...
   }
}

答案 2 :(得分:4)

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

这将完成所需的工作。

        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"
            );

感谢。

答案 3 :(得分:1)

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

嗯......对于自动工厂注入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();
  }
}