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);
    }

person Learning Curve    schedule 09.11.2018    source источник
comment
Попытка полностью имитировать этот интерфейс сложна из-за всех используемых расширений. Вот почему я начал предлагать использовать реальный кеш памяти в связанном ответе.   -  person Nkosi    schedule 09.11.2018
comment
В чем проблема с текущим тестом   -  person Nkosi    schedule 09.11.2018
comment
Ссылка на объект не указывает на экземпляр ошибки объекта в myMemoryCache.Set(Key, keyPairValueData, cacheOptions);   -  person Learning Curve    schedule 09.11.2018
comment
А вы пытались использовать подход из связанного ответа?   -  person Nkosi    schedule 09.11.2018
comment
Создание нового экземпляра MemoryCache, а не насмешка, работает отлично, и я уже сделал это, но не был уверен, следует ли использовать реальные объекты, а не насмешки. Итак, на этой ноте первое предложение @Kenneth отлично работает. Однако еще не пробовал его насмешливое предложение.   -  person Learning Curve    schedule 09.11.2018


Ответы (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>())
         ...

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

person Kenneth    schedule 09.11.2018
comment
Я полагаю, вы хотели сказать, зачем использовать реальный кеш памяти, а почему бы и нет? Пожалуйста, поправьте меня, если я ошибаюсь! - person Learning Curve; 09.11.2018
comment
Нет, я имел в виду, почему бы не использовать настоящий кеш памяти - person Kenneth; 09.11.2018
comment
@Kenneth Я считаю, что первый пример фрагмента отключен, поскольку MemoryCache не имеет конструктора без параметров. - person Nkosi; 09.11.2018
comment
А, да, ты прав. Вам нужно изменить это на .Returns(entryMock.Object), потому что вы хотите вернуть экземпляр, а не фиктивный контейнер. я обновил ответ - person Kenneth; 09.11.2018
comment
Спасибо @Кеннет. В вашем первом фрагменте вы передали 2 параметра, как предложил Нкоси. В моем случае это позволяет мне передавать только MemoryCacheOptions. var cache = новый MemoryCache(new MemoryCacheOptions()); Он отлично работает с параметром options, но мне просто интересно, что я здесь делаю по-другому. Помимо этого небольшого изменения, оба подхода отлично работают для меня. - person Learning Curve; 09.11.2018
comment
Честно говоря, я не совсем уверен, какую конкретную версию MemoryCache вы используете. Я просто просмотрел документы и использовал первую найденную подпись. в любом случае не важно, лишь бы работало. - person Kenneth; 09.11.2018
comment
Я пытаюсь выполнить модульное тестирование того же сценария, и после реализации кода, который я получаю, ICacheEntry не содержит определения для «возврата» здесь: memoryCacheMock.Setup(m =› m.CreateEntry(It.IsAny‹object›()) . Возвращает (entryMock.Object); - person esmehsnj; 31.05.2019