Подтвердить что ты не робот

Правильный способ тестирования 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));
}

Вы порекомендовали бы тестирование по-другому?

4b9b3361

Ответ 1

Если честно, я бы рекомендовал вообще не проверять это взаимодействие.

Я бы подошел к этому контрольному примеру немного по-другому: что вас действительно волнует, так это то, что как только ваш контроллер извлекает данные из вашего ISearchService, он не должен снова запрашивать данные и должен возвращать результат предыдущего вызова.

Тот факт, что 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);
}