#c# #unit-testing #moq #mediatr
Вопрос:
Я пытаюсь написать модульный тест с использованием фреймворка Moq для одного из моих обработчиков mediatrader. У меня есть следующий код. Вкратце, этот обработчик выполняет запрос для данного ключа и возвращает ответ, содержащий ключ и его значение, используя ядро EF. Также существует базовый механизм кэширования. Если ключ найден в кэше, он извлекается из кэша и возвращается.
Обработчик
public GetConfigByKeyRequestHandler(MyContext context, ICacheProvider cacheProvider, IOptions<CacheConfigs> cacheConfigs)
{
this.context = context;
this.cacheProvider = cacheProvider;
this.cacheConfigs = cacheConfigs?.Value;
}
public async Task<ConfigResponse> Handle(GetConfigByKeyRequest request, CancellationToken cancellationToken)
{
ConfigResponse config;
if (!await cacheProvider.ExistsAsync(request.Key))
{
config = await context.Configs
.Where(x.ConfigKey.Equals(request.Key))
.Select(x =>
new ConfigResponse {
ConfigKey = x.ConfigKey,
ConfigValue = x.ConfigValue
})
.FirstOrDefaultAsync(cancellationToken);
if (config is not null)
{
await cacheProvider.PutAsync(new CacheItem<ConfigResponse>(request.Key, config), new CacheOptions
{
ExpireAfter = TimeSpan.FromMinutes(cacheConfigs.ExpireAfterInMinutes).TotalMilliseconds,
ExpireInactive = TimeSpan.FromMinutes(cacheConfigs.ExpireInActiveInMinutes).TotalMilliseconds
});
}
return config;
}
config = await cacheProvider.PullAsync<ConfigResponse>(request.Key);
return config;
}
Я подумал, что мне следует рассмотреть 2 различных сценария:
- Когда ключ найден в кэше
- Когда ключ не найден в кэше и он возвращен из DbContext.
Модульные тесты
private Mock<ICacheProvider> cacheProviderMock;
private IOptions<CacheConfigs> cacheConfigs;
public GetConfigByKeyRequestHandlerTests()
{
cacheProviderMock = new Mock<ICacheProvider>();
cacheConfigs = Options.Create(
new CacheConfigs
{
ExpireAfterInMinutes = 3,
ExpireInActiveInMinutes = 3
});
}
[Fact]
public async Task GetConfigByKeyHandler_WhenKeyIsCached_ShouldReturnConfigByKey()
{
// arrange
var options = new DbContextOptionsBuilder<MyContext>().UseInMemoryDatabase("MyInMemoryDatabase").Options;
var configItems = Enumerable.Range(0, 5).Select(x => new Config
{
ConfigKey = $"key{x}",
ConfigValue = $"value{x}"
});
using (var context = new MyContext(options))
{
await context.Configs.AddRangeAsync(configItems);
await context.SaveChangesAsync();
}
using (var context = new MyContext(options))
{
cacheProviderMock.Setup(x => x.ExistsAsync(It.IsAny<string>())).Returns(Task.FromResult(true));
cacheProviderMock.Setup(x => x.PullAsync<ConfigResponse>("key2"))
.Returns(Task.FromResult(new ConfigResponse
{
ConfigKey = "key2",
ConfigValue = "value2"
}));
var getConfigByKeyHandler = new GetConfigByKeyRequestHandler(context, cacheProviderMock.Object, cacheConfigs);
var getConfigByKeyRequest = new GetConfigByKeyRequest("key2");
// act
var result = await getConfigByKeyHandler.Handle(getConfigByKeyRequest, CancellationToken.None);
// assert
Assert.NotNull(result);
Assert.Equal("key2", result.ConfigKey);
}
}
...
...
С той же логикой у меня есть еще один тест для другого сценария, когда ключ не кэшируется
...
...
[Fact]
public async Task GetConfigByKeyHandler_WhenKeyIsNotCached_ShouldReturnConfigByKey()
{
// arrange
var options = new DbContextOptionsBuilder<MyContext>().UseInMemoryDatabase("MyInMemoryDatabase").Options;
var configItems = Enumerable.Range(0, 5).Select(x => new Config
{
ConfigKey = $"key{x}",
ConfigValue = $"value{x}"
});
using (var context = new MyContext(options))
{
await context.Configs.AddRangeAsync(configItems);
await context.SaveChangesAsync();
}
using (var context = new MyContext(options))
{
cacheProviderMock.Setup(x => x.ExistsAsync(It.IsAny<string>())).Returns(Task.FromResult(false));
var getConfigByKeyHandler = new GetConfigByKeyRequestHandler(context, cacheProviderMock.Object, cacheConfigs);
var getConfigByKeyRequest = new GetConfigByKeyRequest("key2");
// act
var result = await getConfigByKeyHandler.Handle(getConfigByKeyRequest, CancellationToken.None);
// assert
Assert.NotNull(result);
Assert.Equal("key2", result.ConfigKey);
}
}
Я написал 2 модульных теста, которые охватывают сценарии, о которых я упоминал выше, но я не уверен, что они являются разумными, и я не уверен, что их следует тестировать таким образом. У вас есть какие-либо предложения о том, что/как мне следует писать тесты для обработчика, которым я поделился выше?
Комментарии:
1. Я думаю, поскольку у вас есть рабочий код, вы можете найти codereview.stackexchange.com лучший сайт для его улучшения. Вы, скорее всего, получите такие мнения, как 1. Поскольку вы на самом деле не издеваетесь над DbContext, у вас есть интеграционный тест, а не модульный тест, 2. ВЫСУШИТЕ настройки UseInMemoryDatabase 3. MediatR может быть излишним для «обработчиков запросов» и 4. Рассмотрите возможность использования шаблона «Кэш в сторону «, чтобы высушить ваши ветви управления императивным кэшем. Но, конечно, все это всего лишь мнения.
2. Эй. У меня была такая же ситуация в моем проекте. Я решил разделить логику на интеграционные тесты и модульные тесты. Я проверил всю
MediatR
логику с помощью интеграционных тестов. Модульные тесты помогли мне протестировать логику внутри обработчиков. В результате вы можете объединить два подхода тестов для достижения лучших результатов 🙂