Я понимаю, что 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);
}
В чем проблема с текущим тестом
Ссылка на объект не установлена на экземпляр ошибки объекта в myMemoryCache.Set («Ключ», keyPairValueData, cacheOptions);
А вы пробовали использовать подход из связанного ответа?
Создание нового экземпляра MemoryCache, скорее насмешливого, работает отлично, и я уже сделал это, но не был уверен, следует ли использовать реальные объекты, а не макеты. Итак, первое предложение @Kenneth работает отлично. Однако еще не пробовал его насмешливое предложение.





Первое, что я хотел бы сказать, - почему бы не использовать реальный кеш памяти? Он будет намного лучше проверять поведение, и нет необходимости его высмеивать:
// 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>())
...
Когда вы это сделаете, в макете будет вызван метод расширения, и он вернет фиктивную запись. Вы можете изменить реализацию и заставить ее делать все, что захотите.
Я полагаю, вы хотели сказать, почему использовать реальный кеш памяти, а не «почему бы и нет»? Пожалуйста, поправьте меня, если я ошибаюсь!
Нет, я имел в виду "почему бы не использовать реальный кеш памяти"
@Kenneth Я считаю, что первый пример фрагмента отключен, поскольку у MemoryCache нет конструктора без параметров.
Ах да, ты прав. Вам нужно изменить это на .Returns(entryMock.Object), потому что вы хотите вернуть экземпляр, а не фиктивный контейнер. Я обновил ответ
Спасибо, @Kenneth. В вашем первом фрагменте вы передали 2 параметра, как предложил Nkosi. В моем случае это позволяет мне передавать только MemoryCacheOptions. var cache = new MemoryCache (новый MemoryCacheOptions ()); Он отлично работает с параметром options, но мне просто интересно, что я здесь делаю по-другому. Помимо этого небольшого изменения, оба подхода у меня работают нормально.
Честно говоря, я не совсем уверен, какую именно версию MemoryCache вы используете. Я просто просмотрел документы и использовал первую найденную мной подпись. в любом случае не важно, пока он работает.
Я пытаюсь выполнить модульное тестирование того же сценария, и после реализации кода я получаю «ICacheEntry не содержит определения для« Возврат »» здесь: memoryCacheMock.Setup (m => m.CreateEntry (It.IsAny <object> () ) .Returns (entryMock.Object);
Пытаться полностью имитировать этот интерфейс сложно из-за всех используемых расширений. Вот почему я начал предлагать использовать фактический кеш памяти в связанном ответе.