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?
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.
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.
Unit Testing Using XUnit And MOQ In ASP.NET Core.
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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With