【问题标题】:How to Mock (with Moq) Unity methods如何模拟(使用 Moq)Unity 方法
【发布时间】:2011-04-17 00:45:09
【问题描述】:

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

欢迎提出任何想法。

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

【问题讨论】:

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


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

    移除对 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)
       {
           ...
       }
    }
    

    【讨论】:

    • 在我的情况下,我也需要 Unity 用于其他目的......(最近更新了原始帖子)。我可能可以提取的不是日志而是日志工厂,并检查它是否调用了“GetLogObject”方法......谢谢你的想法。
    【解决方案3】:

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

    这将完成所需的操作。

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

    谢谢。

    【讨论】:

      【解决方案4】:

      我将不得不不同意这两个答案。 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();
        }
      }
      

      【讨论】:

      • 我并不是说使用 IoC 是不合法的,只是它使使用直接依赖项进行测试变得更容易。我的示例中实际使用了工厂场景。它可以使用 Unity 的自动工厂功能来实现,而无需引用 IoC。
      • @TheCodeKing 重新阅读您的帖子。我不知道自动工厂支持。再加上InjectionFactory,您可以不再需要注入容器。我把它全部收回:)
      • 我认为在单元测试中使用测试配置的 IoC 容器没有问题,但大多数时候这样做只会引入复杂性。正如你所建议的,我会在 90% 的时间里使用手动注入(在测试中,而不是在生产代码中)。
      猜你喜欢
      • 2010-10-08
      • 2010-11-12
      • 2011-01-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多