Click here to Skip to main content
14,638,759 members
Articles » Platforms, Frameworks & Libraries » .NET Framework » General
Tip/Trick
Posted 26 May 2020

Stats

3.4K views
1 bookmarked

The Forgotten Functionality of Moq

Rate this:
4.20 (2 votes)
Please Sign up or sign in to vote.
4.20 (2 votes)
26 May 2020CPOL
Some functionality of Moq can be really important but can also be easily forgotten as explained here
Much functionality of Moq is obvious but some of it is not. This article gives an explanation of it and explains how it improves your code and makes production issues less likely.

Introduction

An explanation is given of how Moq is often used and how this way of working can be improved.

Background

It will be really helpful if you have some experience with xUnit, mocking and fixtures in .NET Core. The tests shown here are written for .NET Core but most of the code can be used in situations where another unit testing framework is used. If you are not familiar with fixtures, I recommend reading this article. If you are not familiar with Moq, I recommend reading this article.

Using the Code

We start with code we want to test:

[HttpGet("{queryEntry}", Name = "GetNumberOfCharacters")]
public async Task<ActionResult<int>> GetNumberOfCharacters(string queryEntry)
{
    var numberOfCharacters = 
        await _searchEngineService.GetNumberOfCharactersFromSearchQuery(queryEntry);
    return Ok(numberOfCharacters);
}

A unit test can be made easily with AutoFixture.AutoMoq (NuGet package here). The mock is created with the Freeze method in the arrange part of the test and verified and the end of the test with the Verify method. The Create method is called in between to create the instance that holds the method we want to test. This is how the code looks like:

[Fact]
public async Task GetTest()
{
    // arrange
    var fixture = new Fixture().Customize(new AutoMoqCustomization());
    var service = fixture.Freeze<Mock<ISearchEngineService>>();
    service.Setup(a => a.GetNumberOfCharactersFromSearchQuery(It.IsNotNull<string>()))
        .ReturnsAsync(8);

    var controller = fixture.Build<SearchEngineController>().OmitAutoProperties().Create();

    // act
    var response = await controller.GetNumberOfCharacters("Hoi");

    // assert
    Assert.Equal(8, ((OkObjectResult)response.Result).Value);
    service.Verify(s => s.GetNumberOfCharactersFromSearchQuery("Hoi"), Times.Once);
}

In addition, we can create an integration test (instead of  only a unit test) that just resolves all dependencies as set from the Startup class and just replaces the single dependency we want to mock. The main advantage is that we get better code coverage (since the Startup class and Program class are triggered). The main disadvantage is that we have less isolation so this generally will not replace a unit test. This can be done with IntegrationFixture (NuGet package here) and this is how the code looks like:

[Fact]
public async Task GetTestFreeze()
{
    // arrange
    await using (var fixture = new Fixture<Startup>())
    {
        var service = fixture.Freeze<Mock<ISearchEngineService>>();
        service.Setup(a => a.GetNumberOfCharactersFromSearchQuery(It.IsNotNull<string>()))
            .ReturnsAsync(8);

        var controller = fixture.Create<SearchEngineController>();

        // act
        var response = await controller.GetNumberOfCharacters("Hoi");

        // assert
        Assert.Equal(8, ((OkObjectResult) response.Result).Value);
        service.Verify(s => s.GetNumberOfCharactersFromSearchQuery("Hoi"), Times.Once);
    }
}

This code may look fine but in both tests, a mistake is made. We did not verify if any other calls are done on the mock. This may sound irrelevant but it is not. Other calls can simply cause side effects which is often the main cause of production issues. This is the extra code we need at the end of both tests:

service.VerifyNoOtherCalls();

It is easy to forget this. AutoFixture.AutoMoq automatically comes with Moq 4.7 which does not support this method. To be able to call it, a more recent version (at least 4.8) needs to be installed separately. Moreover, IntegrationFixture does not come with any Moq version itself so also for this situation, you need to install it separately. This method really helps preventing side effects but is easy to forget. If you want to see all code: it is on GitHub.

Points of Interest

I learned this method the hard way. I discovered a real production issue caused by a real side effect which I could have discovered before in case I would have used the VerifyNoOtherCalls method. Hopefully, this article helps you not make the same mistake I made.

History

  • 26th May, 2020: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Daan Acohen
Software Developer (Senior)
Netherlands Netherlands
I am a self-employed software engineer working on .NET Core. I love TDD.

Comments and Discussions

 
-- There are no messages in this forum --