正确的测试方法 ASP.NET Core IMemoryCache
Proper way of testing ASP.NET Core IMemoryCache
我正在编写一个简单的测试用例来测试我的控制器在调用我的服务之前调用缓存。我正在使用 xUnit 和 Moq 来完成任务。
我遇到了一个问题,因为 GetOrCreateAsync<T>
是一种扩展方法,框架无法模拟这些方法。我依靠内部细节来弄清楚我可以模拟 TryGetValue
而不是我的测试(参见 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);
}
我无法入睡,因为我正在查看 MemoryCache 实现细节并且它随时可能更改。
供参考,这是 SUT 代码:
public async Task<MyViewModel> Search(SearchRequestViewModel request)
{
return await cache.GetOrCreateAsync(request.Serialized(), (e) => search.FindAsync(request));
}
您会推荐任何不同的测试吗?
老实说,我建议根本不要测试这种交互。
我会以不同的方式处理这个测试用例:您真正关心的是一旦您的控制器从您的 ISearchService
检索数据,它不应该再次请求数据并且应该 return上一次调用的结果。
在幕后使用 IMemoryCache
的事实只是一个实现细节。我什至懒得为它设置一个测试替身,我只会使用 Microsoft.Extensions.Caching.Memory.MemoryCache
对象的一个实例。
我的新测试看起来像这样:
[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);
}
我正在编写一个简单的测试用例来测试我的控制器在调用我的服务之前调用缓存。我正在使用 xUnit 和 Moq 来完成任务。
我遇到了一个问题,因为 GetOrCreateAsync<T>
是一种扩展方法,框架无法模拟这些方法。我依靠内部细节来弄清楚我可以模拟 TryGetValue
而不是我的测试(参见 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);
}
我无法入睡,因为我正在查看 MemoryCache 实现细节并且它随时可能更改。
供参考,这是 SUT 代码:
public async Task<MyViewModel> Search(SearchRequestViewModel request)
{
return await cache.GetOrCreateAsync(request.Serialized(), (e) => search.FindAsync(request));
}
您会推荐任何不同的测试吗?
老实说,我建议根本不要测试这种交互。
我会以不同的方式处理这个测试用例:您真正关心的是一旦您的控制器从您的 ISearchService
检索数据,它不应该再次请求数据并且应该 return上一次调用的结果。
在幕后使用 IMemoryCache
的事实只是一个实现细节。我什至懒得为它设置一个测试替身,我只会使用 Microsoft.Extensions.Caching.Memory.MemoryCache
对象的一个实例。
我的新测试看起来像这样:
[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);
}