Moq模拟调用使用设置时返回null

6

我正在使用Moq为C#应用编写测试。我的测试初始化器包含以下代码:

UnityContainer unityContainer = new UnityContainer();

_serviceMock = new Mock<IService>();
_serviceMock.Setup(mock => mock.GetSearchInfoAsync(It.IsAny<CancellationToken>(), It.IsAny<IEnumerable<string>>(), It.IsAny<identifierType>(), It.IsAny<bool>())).Callback(() => _count++);
unityContainer.RegisterInstance(typeof(IService), _serviceMock.Object, new ContainerControlledLifetimeManager());

我希望测试只调用一次的函数。我尝试以下方式:
int _count = 0;

[TestMethod]
public void Properties_Test()
{
    _serviceMock.Verify(mock => mock.GetSearchInfoAsync(It.IsAny<CancellationToken>(), It.IsAny<IEnumerable<string>>(), It.IsAny<identifierType>(), It.IsAny<bool>()), Times.Exactly(1), "Invocation was performed " + _count + " times but was expected only once!");
}

这是实际调用该方法的方式:
private void Search(string queryValue, identifierType identifierType)
{
    CancellationToken cancellationToken;

    lock (_syncLock)
    {
        _cancellationTokenSource.Cancel();
        _cancellationTokenSource = new CancellationTokenSource();
        cancellationToken = _cancellationTokenSource.Token;
    }

    IService Service = ServiceLocator.Current.GetInstance<IService>();

    Service.GetSearchInfoAsync(cancellationToken, new[] {queryValue}, identifierType)
        .ContinueWith(
            task =>
            {
                // Do stuff
            }, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default);
}

问题在于,如果我按照上述详细说明使用此行代码,
_serviceMock.Setup(mock => mock.GetSearchInfoAsync(It.IsAny<CancellationToken>(), It.IsAny<IEnumerable<string>>(), It.IsAny<identifierType>(), It.IsAny<bool>())).Callback(() => _count++);

这个返回null并且导致NullPointerException异常:

Service.GetSearchInfoAsync(cancellationToken, new[] {queryValue}, identifierType)

然而,如果我注释掉那一行,测试就可以正常运行(尽管不能计算调用次数)。

我做错了什么?这是我第一次使用 Moq,就我所知,我已经正确实现了计数功能。

编辑:根据 Chris Sinclair 的建议,我已将初始化程序更改为以下内容,这解决了问题:

UnityContainer unityContainer = new UnityContainer();

_serviceMock = new Mock<IService>();
Task<IEnumerable<ISearchResult>> task = new Task<IEnumerable<ISearchResult>>(Enumerable.Empty<ISearchResult>);
_serviceMock.Setup(mock => mock.GetSearchInfoAsync(It.IsAny<CancellationToken>(), It.IsAny<IEnumerable<string>>(), It.IsAny<identifierType>(), It.IsAny<bool>())).Returns(task).Callback(() => _count++);
unityContainer.RegisterInstance(typeof(IService), _serviceMock.Object, new ContainerControlledLifetimeManager());

3
当你“设置”该方法时,你设置了一个回调函数,但没有设置返回值。如果我猜测的没错,这将使该方法返回返回类型的默认值,看起来似乎是某种“Task<>”(因此默认值将为“null”)。你的“Search”方法调用了被模拟的方法,该方法返回“null”,然后在该空引用上调用“.ContinueWith()”。也许尝试为你的设置添加一个“.Returns()”,它创建一个虚拟的“Task<>”?抱歉,我不确定100%的正确性。 - Chris Sinclair
1
你是正确的,那解决了!请把你的回复提炼成答案,这样我就可以将其标记为解决方案了!谢谢! - Vlad Schnakovszki
1个回答

4
当你“设置”该方法时,你设置了一个回调函数但没有提供返回值。因此,当模拟方法被调用时,它将返回返回类型的默认值(在本例中,Task<>类型将导致返回值为null)。因此,当你的Search方法调用模拟的GetSearchInfoAsync方法时,它会接收到一个null引用,在稍后尝试调用.ContinueWith时自然会失败。
尝试添加.Returns(),向你的模拟方法提供一个虚拟的Task<>
_serviceMock.Setup(mock => mock.GetSearchInfoAsync(It.IsAny<CancellationToken>(), It.IsAny<IEnumerable<string>>(), It.IsAny<identifierType>(), It.IsAny<bool>()))
    .Returns(new Task<IEnumerable<ISearchResult>>(Enumerable.Empty<ISearchResult>))
    .Callback(() => _count++);

谢谢!我决定放弃计数和设置,因为我注意到它们并不真正需要。看一下,看看你能否回答这个问题:http://stackoverflow.com/questions/34205261/moq-verify-number-of-calls-works-if-test-called-alone-but-not-if-called-along-o - Vlad Schnakovszki
很高兴能帮忙。很抱歉关于你的另一个问题,我不是很清楚。我的第一个猜测是跳过共享资源,并尝试在失败的测试中使用本地变量(没有字段或共享资源)来设置所有内容。如果测试现在通过了,那么你就知道设置方式存在问题。如果仍然失败,则说明测试本身存在问题。 - Chris Sinclair

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