使用匿名函数作为参数验证函数调用

4
我有一个类,一个服务和两个接口:
public class MyBasicObject
{
    public MyBasicObject() { }

    public int Id { get; set; }        
    public string Name { get; set; }
}

public interface ICacheProvider
{
    T Get<T>(string key, Func<T> fetcher) where T:class;
}

public interface IMyBasicObjectRepository
{
    MyBasicObject GetByName(string name);
}

public class MyBasicObjectService
{
    public MyBasicObjectService(ICacheProvider cacheProvider, 
                                IMyBasicObjectRepository repository)
    {
        CacheProvider = cacheProvider;
        MyBasicObjectRepository = repository;
    }

    public ICacheProvider CacheProvider { get; set; }
    public IMyBasicObjectRepository MyBasicObjectRepository { get; set; }

    public MyBasicObject GetByName(string name)
    {
        return CacheProvider.Get<MyBasicObject>(name, () =>
                                MyBasicObjectRepository.GetByName(name));
    }
}

使用RhinoMocks,我希望能够验证当执行MyBasicObjectService.GetByName("AnUniqueName")时,也会执行CacheProvider.Get("AnUniqueName", () => MyBasicObjectRepository.GetByName("AnUniqueName"))。我已经设置了一个如下的fixture:

[TestFixture]
    public class MyBasicObjectServiceFixture
    {
        [Test]
        public void GetByNameShouldCallCacheProviderFunction()
        {
            // Arrange
            MockRepository mock = new MockRepository();
            IMyBasicObjectRepository repo = mock.DynamicMock<IMyBasicObjectRepository>();
            ICacheProvider cacheProvider = mock.DynamicMock<ICacheProvider>();
            MyBasicObjectService service = new MyBasicObjectService(cacheProvider, repo);

            cacheProvider.Expect(p => p.Get<MyBasicObject>("AnUniqueName", () => repo.GetByName("AnUniqueName")));

            mock.ReplayAll();

            // Act
            var result = service.GetByName("AnUniqueName");

            // Assert
            mock.VerifyAll();
        }
    }

我原以为这个测试会通过,但运行之后,断言失败了,并提示我cacheProvider.Expect中定义的函数未被调用。我是不是在模拟和测试带有Func<>参数的方法时漏掉了什么?
编辑:
所以如果我这样做:
cacheProvider.Expect(p => p.Get<MyBasicObject>("AnUniqueName", () => repo.GetByName("AnUniqueName"))).IgnoreArguments();

也就是说,在expect调用的末尾添加IgnoreArguments()方法即可。当我这样做时,测试顺利通过。因此我假设问题出在传入的参数上。在expect中调用缓存提供程序方法时,是否有什么我做错了导致匿名方法无法使用?
2个回答

1
问题在于两个匿名方法(一个在Expect中,另一个在GetByName中创建)是两个不同的对象,因此它们不相等。您可以通过部分匹配参数来解决这个问题,像这样:
cacheProvider.Expect(p => p.Get<MyBasicObject>(Arg<string>.Is.Equal("AnUniqueName"), Arg <Func<MyBasicObject>>.Is.NotNull));

在这种情况下,我的问题是 - 是否有可能断言传递的匿名函数与服务上的函数匹配?我意识到两个匿名函数永远不会相等,但我正在努力找出一种可单元测试的方法来确保服务上的匿名函数是正确的。有什么想法吗? - antinescience
在实现中,服务将调用缓存提供程序,并在缓存未命中的情况下执行传递的匿名函数。我想我可以为单元测试专门实现缓存提供程序,然后断言,在缓存未命中的情况下调用了存储库方法 - 这是一个可行的做法吗?实际上,我将需要一个绕过缓存的ICacheProvider实现。 - antinescience
我添加了一个回答,说明我最终做了什么。 - antinescience

0

我最终为测试所做的是:

[TestFixture]
public class MyBasicObjectServiceFixture
{
    [Test]
    public void GetByNameShouldCallCacheProviderFunction()
    {
        // Arrange
        MockRepository mock = new MockRepository();
        IMyBasicObjectRepository repo = mock.DynamicMock<IMyBasicObjectRepository>();
        ICacheProvider cacheProvider = mock.DynamicMock<ICacheProvider>();
        MyBasicObjectService service = new MyBasicObjectService(cacheProvider, repo);

        cacheProvider.Expect(p => p.Get<MyBasicObject>(Arg<string>.Is.Equal("AnUniqueName"), Arg<Func<MyBasicObject>>.Is.NotNull))
                .WhenCalled(call => 
                    {
                        var repoCall = (Func<MyBasicObject>)call.Arguments[1];
                        repoCall.Invoke();
                    });
            repo.Expect(c => c.GetByName("AnUniqueName"));

        mock.ReplayAll();

        // Act
        var result = service.GetByName("AnUniqueName");

        // Assert
        mock.VerifyAll();
    }
}

这个方法对于我的使用场景非常有效(在缓存未命中时调用数据库检索,并确保服务在那个时候使用正确的存储库调用),但如果您不打算立即调用匿名函数,这种方法可能不是很好。我相信还有其他使用.WhenCalled的替代方案,但目前这个方法对我来说已经足够了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接