Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Proper way of testing ASP.NET Core IMemoryCache

I'm writing a simple test case that tests that my controller calls the cache before calling my service. I'm using xUnit and Moq for the task.

I'm facing an issue because GetOrCreateAsync<T> is an extension method, and those can't be mocked by the framework. I relied on internal details to figure out I can mock TryGetValue instead and get away with my test (see https://github.com/aspnet/Caching/blob/c432e5827e4505c05ac7ad8ef1e3bc6bf784520b/src/Microsoft.Extensions.Caching.Abstractions/MemoryCacheExtensions.cs#L116)

[Theory, AutoDataMoq]
public async Task GivenPopulatedCacheDoesntCallService(
    Mock<IMemoryCache> cache,
    SearchRequestViewModel input,
    MyViewModel expected)
{
    object expectedOut = expected;
    cache
        .Setup(s => s.TryGetValue(input.Serialized(), out expectedOut))
        .Returns(true);
    var sut = new MyController(cache.Object, Mock.Of<ISearchService>());
    var actual = await sut.Search(input);
    Assert.Same(expected, actual);
}

I can't sleep with the fact that I'm peeking into the MemoryCache implementation details and it can change at any point.

For reference, this is the SUT code:

public async Task<MyViewModel> Search(SearchRequestViewModel request)
{
    return await cache.GetOrCreateAsync(request.Serialized(), (e) => search.FindAsync(request));
}

Would you recommend testing any differently?

like image 263
Martín Coll Avatar asked Nov 18 '16 19:11

Martín Coll


People also ask

What is the best test framework for .NET Core?

xUnit test is the best Unit testing framework for . Net programming languages like C#, VB.Net, and F#. xUnit derives its structure and functionality from SUnit of Smalltalk.

How do I run a test in .NET Core?

Let us now click on Run All button in Test Explorer. It will first build the code and the run the test and you will see the total time taken by the test. Let us change the test method so that we can see the output when the test fails. Let us execute the test again by clicking on the Run All button link.

Which method is used in the unit testing application in .NET Core?

Unit Testing Using XUnit And MOQ In ASP.NET Core.


1 Answers

To be honest I would recommend not to test this interaction at all.

I would approach this test case a bit differently: what you really care about is that once your controller retrieved data from your ISearchService it shouldn't request the data again and should return the result from the previous call.

The fact that an IMemoryCache is used behind the scenes is just an implementation detail. I wouldn't even bother setting up a test double for it, I would just use an instance of the Microsoft.Extensions.Caching.Memory.MemoryCache object.

My new test would look something like this:

[Theory]
public async Task GivenResultAlreadyRetrieved_ShouldNotCallServiceAgain()
{
    // Arrange
    var expected = new MyViewModel();

    var cache = new MemoryCache(new MemoryCacheOptions());
    var searchService = new Mock<ISearchService>();

    var input = new SearchRequestViewModel();

    searchService
        .SetupSequence(s => s.FindAsync(It.IsAny<SearchRequestViewModel>()))
        .Returns(Task.FromResult(expected))
        .Returns(Task.FromResult(new MyViewModel()));

    var sut = new MyController(cache, searchService.Object);

    // Act
    var resultFromFirstCall = await sut.Search(input);
    var resultFromSecondCall = await sut.Search(input);

    // Assert
    Assert.Same(expected, resultFromFirstCall);
    Assert.Same(expected, resultFromSecondCall);
}
like image 177
Botond Botos Avatar answered Sep 19 '22 18:09

Botond Botos