如何使用Moq测试lambda函数?

时间:2010-09-20 14:48:19

标签: .net unit-testing mocking lambda moq

有一个功能:

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

有必要为此编写一个UnitTest(带有Moq)并检查是否有正确的参数传递给'MaCacheClass.GetObject'。

它应该是这样的:

[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-function'的参数? 还有其他选择吗?

欢迎任何想法。

2 个答案:

答案 0 :(得分:1)

其中一个选项可能是:在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());
}

为'MyCacheClass'的'GetObject'方法提供额外测试,检查它是否调用IGetObject参数的'GetObject'方法...

P.S。有点复杂的解决方案...所以,如果有人看到更好的解决方案,请指教!

答案 1 :(得分:1)

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

或者你可以使用Callback机制实际调用传入缓存的lambda,然后验证是否在该mock上调用了IWorkClass.MyWorkMethod。这与缓存和工作类最终的使用方式相匹配,当缓存很有价值时,它也可以提供有价值行为的更好示例,而不仅仅是单独测试方法。

第三,你可以使用It.IsAny<Func<string, void>>()并接受你必须通过检查得到这一点(只要它更容易正确而不是错,这通常是正常的)。我通常命名我的委托或lambda签名,所以我可能有这个Func<string, void>的错误。它会成功。

作为第四种选择,推出一个小的存根缓存而不是使用模拟框架,然后从中获取并调用lambda。虽然我喜欢Moq及其Java对应Mockito,但有时我发现当我们接受这些工具不支持我们正在尝试做的事情时,单元测试更容易阅读和维护他们。它只有几行代码,并且存根缓存对其他单元测试也很有用。