Я пытаюсь высмеять следующий метод службы кэширования, используя Moq:
Task<TItem> GetOrCreateAsync<TItem>(string key, Func<Task<TItem>> factory)
Этот метод обычно проверяет кэш на наличие записи и возвращает ее, а если не найден, создает новый экземпляр, вызывая фабрику. Я хочу создать макет, который просто вызывает фабрику и возвращает результат вызывающей стороне.
Вот минимальный воспроизводимый пример (LINQPad Instant Share):
var mock = new Mock<ICache>();
mock.Setup(cache => cache.GetOrCreateAsync(It.IsAny<string>(), It.IsAny<Func<Task<It.IsAnyType>>>()).Result)
.Returns((string b, Func<Task<object>> c) => c().Result);
mock.Object.GetOrCreateAsync("test", () => Task.FromResult(123));
public interface ICache
{
Task<TItem> GetOrCreateAsync<TItem>(string key, Func<Task<TItem>> factory);
}
Когда я запускаю вышеуказанное, я получаю InvalidCastException: Unable to cast object of type 'System.Threading.Tasks.Task'1[Moq.It+IsAnyType]' to type 'System.Threading.Tasks.Task'1[System.Int32]'.
Есть идеи?
Не могу воспроизвести. Пожалуйста, опубликуйте минимальный воспроизводимый пример.
Добавлен минимальный воспроизводимый пример.





Для насмешки возвращаемого значения асинхронной функции следует использовать ReturnsAsync, а не Returns. Как только вы это исправите, я также буду использовать более конкретные объекты в макете на основе конкретного теста.
Согласно github.com/devlooped/moq/wiki/Quickstart#async-methods ReturnsAsync больше не требуется.
Узнавайте что-то новое каждый день!
Мне любопытно, почему вы не вернули объект вместо лямбда-функции. Похоже, ему не нравится разница между тем, что вы заявили, что это необходимо в качестве входных данных в макете, и внутренними возвратами лямбды.
Сообщение об ошибке уже объясняет, в чем проблема. Task<It+AnyType> несовместим с Task<int>. Самый простой способ решить эту проблему — использовать аргументы соответствующего типа:
[Fact]
public async Task Repro()
{
var mock = new Mock<ICache>();
mock.Setup(cache => cache.GetOrCreateAsync(
It.IsAny<string>(),
It.IsAny<Func<Task<int>>>()))
.Returns((string b, Func<Task<int>> c) => c());
await mock.Object.GetOrCreateAsync("test", () => Task.FromResult(123));
}
Этот тест проходит без каких-либо исключений.
Если вы хотите иметь какую-то многоразовую настройку, используйте дженерики так, как они были задуманы. Возможно, проще всего сначала настроить вспомогательный метод:
private static void SetupCache<T>(Mock<ICache> mock, T returnValue)
{
mock.Setup(cache => cache.GetOrCreateAsync(
It.IsAny<string>(),
It.IsAny<Func<Task<T>>>()))
.ReturnsAsync(returnValue);
}
Обратите внимание, что этот метод является универсальным в T, а это означает, что вы можете вызывать его с аргументами разных типов:
[Fact]
public async Task CacheInt()
{
var mock = new Mock<ICache>();
SetupCache(mock, 42);
var actual =
await mock.Object.GetOrCreateAsync("test", () => Task.FromResult(123));
Assert.Equal(42, actual);
}
[Fact]
public async Task CacheString()
{
var mock = new Mock<ICache>();
SetupCache(mock, "foo");
var actual =
await mock.Object.GetOrCreateAsync("test", () => Task.FromResult("bar"));
Assert.Equal("foo", actual);
}
Оба эти теста пройдены.
Спасибо, Марк. Я надеялся на более общее решение, использующее функцию It.IsAnyType, но вместо этого ваше решение является наиболее простым.
Какая подпись у
GetOrCreateAsync?