Vlad Schnakovszki
Vlad Schnakovszki

Reputation: 8601

Moq mocked call returns null if using setup

I am writing tests for a C# application, using Moq. My test initialiser has the following code:

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

I want to test that a call is made only once. I am trying it like this:

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

This is the method where it actually gets called:

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

The problem is that if I use this line as detailed above,

_serviceMock.Setup(mock => mock.GetSearchInfoAsync(It.IsAny<CancellationToken>(), It.IsAny<IEnumerable<string>>(), It.IsAny<identifierType>(), It.IsAny<bool>())).Callback(() => _count++);

this returns null and generates a NullPointerException:

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

However, if I comment out that line, the tests run fine (albeit not counting the number of calls).

What am I doing wrong? This is my first time using Moq for this and as far as I can tell I've implemented the count functionality correctly.

EDIT: Following Chris Sinclair's suggestion, I've changed the initialiser to this, which fixed the issue:

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

Upvotes: 6

Views: 3687

Answers (1)

Chris Sinclair
Chris Sinclair

Reputation: 23198

When you "Setup" the method, you set a callback but you don't provide a return value. As such, when the mocked method is called, it will return the default value for the return type (in this case, a Task<> type will result in a null return value). As such, when your Search method calls your mocked GetSearchInfoAsync method, it receives a null reference which naturally fails when it later attempts to invoke .ContinueWith on it.

Try adding a .Returns() which feeds a dummy Task<> to your mocked method:

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

Upvotes: 4

Related Questions