XUnit как имитировать IMemoryCache ASP.NET Core

Я понимаю, что IMemoryCache.Set - это метод расширения, поэтому его нельзя высмеивать. Люди предоставили обходные пути для такой ситуации, например, NKosi здесь. Мне интересно, как я могу добиться этого для моего уровня доступа к данным, когда мой MemoryCache возвращает значение, а когда он не найден, он получает данные из базы данных, устанавливает его в MemoryCache и возвращает требуемое значение.

    public string GetMessage(int code)
    {
        if (myMemoryCache.Get("Key") != null)
        {
            var messages= myMemoryCache.Get<IEnumerable<MyModel>>("Key");
            return messages.Where(x => x.Code == code).FirstOrDefault().Message;
        }

        using (var connection = dbFactory.CreateConnection())
        {
            var cacheOptions = new MemoryCacheEntryOptions { SlidingExpiration = TimeSpan.FromHours(1) };
            const string sql = @"SELECT Code, Message FROM MyTable";

            var keyPairValueData = connection.Query<KeyPairValueData>(sql);

            myMemoryCache.Set("Key", keyPairValueData, cacheOptions );

            return keyPairValueData.Where(x => x.Code == code).FirstOrDefault().Message;
        }
    }

Ниже приведен мой модульный тест - и, конечно, он не работает, так как я не могу издеваться над IMemoryCache.

    [Fact]
    public void GetMessage_ReturnsString()
    {
        //Arrange
        // Inserting some data here to the InMemoryDB

        var memoryCacheMock = new Mock<IMemoryCache>();

        //Act
        var result = new DataService(dbConnectionFactoryMock.Object, memoryCacheMock.Object).GetMessage(1000);

        //assert xunit
        Assert.Equal("Some message", result);
    }

Пытаться полностью имитировать этот интерфейс сложно из-за всех используемых расширений. Вот почему я начал предлагать использовать фактический кеш памяти в связанном ответе.

Nkosi 09.11.2018 12:30

В чем проблема с текущим тестом

Nkosi 09.11.2018 13:20

Ссылка на объект не установлена ​​на экземпляр ошибки объекта в myMemoryCache.Set («Ключ», keyPairValueData, cacheOptions);

Learning Curve 09.11.2018 13:45

А вы пробовали использовать подход из связанного ответа?

Nkosi 09.11.2018 13:51

Создание нового экземпляра MemoryCache, скорее насмешливого, работает отлично, и я уже сделал это, но не был уверен, следует ли использовать реальные объекты, а не макеты. Итак, первое предложение @Kenneth работает отлично. Однако еще не пробовал его насмешливое предложение.

Learning Curve 09.11.2018 14:07
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
5
3 743
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Первое, что я хотел бы сказать, - почему бы не использовать реальный кеш памяти? Он будет намного лучше проверять поведение, и нет необходимости его высмеивать:

// Arrange
var memCache = new MemoryCache("name", new NameValueCollection());

//Act
var result = new DataService(dbConnectionFactoryMock.Object, memCache).GetMessage(1000);

// Assert: has been added to cache
memCache.TryGetValue("Key", out var result2);
Assert.Equal("Some message", result2);

// Assert: value is returned
Assert.Equal("Some message", result);

Если вы действительно хотите поиздеваться над этим, вот руководство, как это сделать:

Поскольку это метод расширения, вам необходимо убедиться, что он может вызываться как есть. Что происходит в вашем случае, так это то, что метод расширения вызывает макет. Поскольку вы не обеспечиваете ожидаемого поведения, он, вероятно, потерпит неудачу.

Вам нужно посмотреть код метода расширения, проверить, к чему он обращается, а затем убедиться, что ваш макет соответствует ожидаемому поведению. Код доступен здесь: https://github.com/aspnet/Caching/blob/master/src/Microsoft.Extensions.Caching.Abstractions/MemoryCacheExtensions.cs#L77

Это код:

public static TItem Set<TItem>(this IMemoryCache cache, object key, TItem value, MemoryCacheEntryOptions options)
    {
        using (var entry = cache.CreateEntry(key))
        {
            if (options != null)
            {
                entry.SetOptions(options);
            }

            entry.Value = value;
        }

        return value;
    }

Итак, из этого вы можете видеть, что он обращается к CreateEnty и ожидает от него объект. Затем он вызывает SetOptions и назначает Value записи.

Вы можете издеваться над этим так:

var entryMock = new Mock<ICacheEntry>();
memoryCacheMock.Setup(m => m.CreateEntry(It.IsAny<object>())
               .Returns(entryMock.Object);

// maybe not needed
entryMock.Setup(e => e.SetOptions(It.IsAny<MemoryCacheEntryOptions>())
         ...

Когда вы это сделаете, в макете будет вызван метод расширения, и он вернет фиктивную запись. Вы можете изменить реализацию и заставить ее делать все, что захотите.

Я полагаю, вы хотели сказать, почему использовать реальный кеш памяти, а не «почему бы и нет»? Пожалуйста, поправьте меня, если я ошибаюсь!

Learning Curve 09.11.2018 12:14

Нет, я имел в виду "почему бы не использовать реальный кеш памяти"

Kenneth 09.11.2018 12:27

@Kenneth Я считаю, что первый пример фрагмента отключен, поскольку у MemoryCache нет конструктора без параметров.

Nkosi 09.11.2018 12:35

Ах да, ты прав. Вам нужно изменить это на .Returns(entryMock.Object), потому что вы хотите вернуть экземпляр, а не фиктивный контейнер. Я обновил ответ

Kenneth 09.11.2018 14:59

Спасибо, @Kenneth. В вашем первом фрагменте вы передали 2 параметра, как предложил Nkosi. В моем случае это позволяет мне передавать только MemoryCacheOptions. var cache = new MemoryCache (новый MemoryCacheOptions ()); Он отлично работает с параметром options, но мне просто интересно, что я здесь делаю по-другому. Помимо этого небольшого изменения, оба подхода у меня работают нормально.

Learning Curve 09.11.2018 15:16

Честно говоря, я не совсем уверен, какую именно версию MemoryCache вы используете. Я просто просмотрел документы и использовал первую найденную мной подпись. в любом случае не важно, пока он работает.

Kenneth 09.11.2018 15:26

Я пытаюсь выполнить модульное тестирование того же сценария, и после реализации кода я получаю «ICacheEntry не содержит определения для« Возврат »» здесь: memoryCacheMock.Setup (m => m.CreateEntry (It.IsAny <object> () ) .Returns (entryMock.Object);

esmehsnj 31.05.2019 15:02

Другие вопросы по теме