Это не вопрос, а пример, который я попробовал, где вопросы не задавались. В случае, если кто-нибудь еще попробует этот тип идиотного c модульного тестирования в будущем, это мои выводы:
При попытке реализовать тщательную проверку, поскольку в настоящее время она не поддерживается. NET Core 3.1, но, как указано в документации в нижней части раздела https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-3.1#options -post-configuration :
Готовность к проверке (быстрый запуск при запуске) рассматривается в будущем выпуске.
Вы не можете программно проверить ленивую проверку доступа к рассматриваемой опции, если вы внедрили пользовательскую нетерпеливую проверку.
Вот что я сделал:
Создан класс конфигурации
public class TestOptions : IValidateObject // for eager validation config
{
[Required]
public string Prop { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (string.IsNullOrEmpty(this.Prop))
yield return new ValidationResult($"{nameof(this.Prop)} is null or empty.");
}
}
В мою библиотеку, которую я тестирую, добавлена конфигурация:
public static void AddConfigWithValidation(this IServiceCollection services, Action<TestOptions> options)
{
var opt = new TestOptions();
options(opt);
// eager validation
var validationErrors = opt.Validate(new ValidationContext(opt)).ToList();
if (validationErrors.Any())
throw new ApplicationException($"Found {validationErrors.Count} configuration error(s): {string.Join(',', validationErrors)}");
// lazy validation with validate data annotations from IOptions
services.AddOptions<TestOptions>()
.Configure(o =>
{
o.Prop = opt.Prop
})
.ValidateDataAnnotations();
}
И тест выглядит следующим образом
public class MethodTesting
{
private readonly IServiceCollection _serviceCollection;
public MethodTesting()
{
_serviceCollection = new ServiceCollection();
}
// this works as it should
[Fact]
public void ServiceCollection_Eager_Validation()
{
var opt = new TestOptions { Prop = string.Empty };
Assert.Throws<ApplicationException>(() => _serviceCollection.AddConfigWithValidation(o =>
{
o.Prop = opt.Prop
});
}
// this does not work
[Fact]
public void ServiceCollection_Lazy_Validation_Mock_Api_Start()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("settings.json", optional: false, reloadOnChange: true);
_configuration = builder.Build();
var opt = _configuration.GetSection(nameof(TestOptions)).Get<TestOptions>();
_serviceCollection.AddConfigWithValidation(o =>
{
o.Prop = opt.Prop
});
// try to mock a disposable object, sort of how the API works on subsequent calls
using (var sb = _serviceCollection.BuildServiceProvider())
{
var firstValue = sb.GetRequiredService<IOptionsSnapshot<TestOptions>>().Value;
firstValue.Should().BeEquivalentTo(opt);
}
// edit the json file programmatically, trying to trigger a new IOptionsSnapshot<>
var path = $"{Directory.GetCurrentDirectory()}\\settings.json";
var jsonString = File.ReadAllText(path);
var concreteObject = Newtonsoft.Json.JsonConvert.DeserializeObject<TestObject>(jsonString);
concreteObject.TestObject.Prop = string.Empty;
File.WriteAllText(path, Newtonsoft.Json.JsonConvert.SerializeObject(concreteObject));
using (var sb = _serviceCollection.BuildServiceProvider())
{
// this does not work, as the snapshot is still identical to the first time it is pulled
Assert.Throws<OptionsValidationException>(() => _serviceCollection.BuildServiceProvider().GetRequiredService<IOptionsSnapshot<TestOptions>>().Value);
}
}
// this does not work as well
[Fact]
public void ServiceCollection_Lazy_Validation_Mock_Api_Start_With_Direct_Prop_Assignation()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("settings.json", optional: false, reloadOnChange: true);
_configuration = builder.Build();
var opt = _configuration.GetSection(nameof(TestOptions)).Get<TestOptions>();
_serviceCollection.AddConfigWithValidation(o =>
{
o.Prop = opt.Prop
});
using (var sb = _serviceCollection.BuildServiceProvider())
{
var firstValue = sb.GetRequiredService<IOptionsSnapshot<TestOptions>>().Value;
firstValue.Should().BeEquivalentTo(opt);
}
var prop = _configuration["TestOptions:Prop"];
_configuration["TestOptions:Prop"] = string.Empty;
// this returns a new value
var otherProp = _configuration["TestOptions:Prop"];
using (var sb = _serviceCollection.BuildServiceProvider())
{
// this does not work, the snapshot is not yet modified, however, calling _configuration.GetSection(nameof(TestOptions)).Get<TestOptions>(); does return the new TestOptions.
Assert.Throws<OptionsValidationException>(() => _serviceCollection.BuildServiceProvider().GetRequiredService<IOptionsSnapshot<TestOptions>>().Value);
}
}
public class TestObject
{
public TestOptions TestOptions { get; set; }
}
Мои настройки. json выглядело так:
{
"TestOptions": {
"Prop": "something"
}
}
Решение, чтобы запустить его в качестве теста, состоит в том, чтобы добавить необязательный параметр или перегруженный метод с необязательным параметром, который обеспечивает или не требует проверки d проверить, что ленивая проверка правильности работает, когда нетерпеливый деактивирован.
Обратите внимание, что это не идеально, но метод тестирования для людей, которые хотят проверить, как проверенная и ленивая проверка может быть проверена, когда предоставленные параметры получены из источника, который обновляется, но приложения не перезапускаются.
Если у вас есть предложения, вопросы или вы хотите обсудить данную тему, не стесняйтесь использовать раздел комментариев