代码之家  ›  专栏  ›  技术社区  ›  Budda

如何用Moq测试lambda函数?

  •  4
  • Budda  · 技术社区  · 15 年前

    有一个函数:

    public class MyCacheClass : ICache
    {
        public void T GetObject<T>(Func<T> func)
        {
            T t;
            ...
            t = func();
            ...
            return t;
        }
    }
    
    public class MyWorkClass : IWork
    {
        public Object MyWorkMethod(string value)
        {
            return new object();
        }
    }
    

    这些函数的调用方式如下:

    public class MyTestableClass 
    {
        public void MyTestableFunc(ICache cache, IWorkClass work)
        {
            string strVal="...";
            ...
            Object obj = cache(()=>work.MyWorkMethod(strVal));
            ...
        }
    }
    

    应该是这样的:

    [TestMethod]
    public void MyTest()
    {
        Mock<ICache> mockCache = new Mock<ICache>();
        Mock<IWorkClass> mockWorkClass  = new Mock<IWorkClass>();
    
        MyTestableClass testable = new MyTestableClass();
        testable.MyTestableFunc(mockCache.Object, mockWorkClass.Object);
    
        // should I check if 'MyCacheClass' was called with proper parameter?
        mockCache.Verify(mock=>mock.GetObject(...)).Times.Once());
    }
    

    我如何才能提供参数,将适合作为'lambda函数'? 还有其他选择吗?

    欢迎有任何想法。

    2 回复  |  直到 15 年前
        1
  •  1
  •   Budda    15 年前

    其中一个选项可以是:而不是在Funct参数上传递一些实现与“GetObject”函数接口的对象,例如:

    public interface IGetObject
    {
        T GetObject<T>();
    }
    
    public class MyGetObject : IGetObject
    {
        public MyGetObject()
        {
        }
    
        public void Init(string strVal, Func<string, T> func)
        {
            _strVal = strVal;
            _func = func;
        }
    
        public T GetObject<T>()
        {
            return _func(_strVal);
        }
    }
    
    public class MyCacheClass : ICache
    {
        public void T GetObject<T>(IGetObject getter)
        {
            T t;
            ...
            t = getter.GetObject<T>();
            ...
            return t;
        }
    }
    
    public class MyTestableClass 
    {
        public void MyTestableFunc(ICache cache, IWorkClass work)
        {
            string strVal="...";
            IGetObject getter = UnityContainer.Resolve<IGetObject>();
            getter.Init(strVal, str=>work.MyWorkMethod(str));
            ...
            Object obj = cache.GetObject(getter);
            ...
        }
    }
    

    在这种情况下,我想,我们还需要将UnityContainer注入到测试对象中,因此它将是这样的:

    [TestMethod]
    public void MyTest()
    {
        Mock<ICache> mockCache = new Mock<ICache>();
        Mock<IWorkClass> mockWorkClass  = new Mock<IWorkClass>();
        Mock<IGetObject> mockGetter  = new Mock<IGetObject>();
    
        mockGetter.Setup(mock=>mock.GetObject()).Return(new Object());
    
        MyTestableClass testable = new MyTestableClass();
        testable.MyTestableFunc(mockCache.Object, mockWorkClass.Object);
    
        // should I check if 'MyCacheClass' was called with proper parameter?
        mockCache.Verify(mock=>mock.GetObject(mockGetter.Object)).Times.Once());
    }
    

    另一个有点复杂的解决方案。。。所以,如果有人看到更好的,请建议!

        2
  •  1
  •   Lunivore    15 年前

    您可以更改类,以便将lambda传递到类中,而不是整个 IWorkClass . 然后你就可以存储和验证实际的lambda。

    或者,您可以使用回调机制实际调用传入缓存的lambda,然后验证 IWorkClass.MyWorkMethod 却被那个冒牌货叫来了。这与缓存和工作类在缓存有价值时最终的使用方式相匹配,因此它也可以提供一个更好的示例来说明有价值的行为,而不仅仅是单独测试方法。

    It.IsAny<Func<string, void>>() 并且接受这样一个事实:你必须通过检查来纠正这一点(只要纠正比纠正容易,这通常是可以的)。我通常命名我的代表或lambda签名,这样我就可以得到这个 Func<string, void> 出问题了。会成功的。

    作为第四种选择,推出一个小存根缓存,而不是使用模拟框架,然后从中获取并调用lambda。虽然我很喜欢 Moq 以及它的Java对应物 Mockito