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?
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 theMicrosoft.Extensions.Caching.Memory.MemoryCache
object.My new test would look something like this: