选项 A
如果您正在使用 Microsoft.AspNetCore.Mvc.Testing 库运行测试,您可以使用不同的环境、环境变量或 appsettings 文件,正如一些在 cmets 中提到的那样。
我在 xUnit 下的所有合同/组件/功能(无论您想如何称呼它们)测试都继承自一个基类,该基类抽象出配置,以便测试可以专注于给定/何时/然后部分。
public abstract class Given_When_Then_Test_Async
: IAsyncLifetime
{
public async Task InitializeAsync()
{
await this.Given();
await this.When();
}
public async Task DisposeAsync() => await this.Cleanup();
protected virtual Task Cleanup() => Task.CompletedTask;
protected abstract Task Given();
protected abstract Task When();
}
public static class ServiceCollectionExtensions
{
public static IServiceCollection Replace<TService>(
this IServiceCollection services,
Func<IServiceProvider, TService> implementationFactory,
ServiceLifetime lifetime)
where TService : class
{
var descriptorToRemove = services.FirstOrDefault(d => d.ServiceType == typeof(TService));
services.Remove(descriptorToRemove);
var descriptorToAdd = new ServiceDescriptor(typeof(TService), implementationFactory, lifetime);
services.Add(descriptorToAdd);
return services;
}
}
public abstract class FunctionalTest
: Given_When_Then_Test_Async
{
private readonly IServiceProvider _serviceProvider;
protected HttpClient HttpClient { get; }
protected IConfiguration Configuration { get; }
protected FunctionalTest()
{
var server =
new TestServer(
new WebHostBuilder()
.UseStartup<Startup>()
.UseCommonConfiguration()
.UseEnvironment("Test") // here I specify that my environment is called Test, so it'll look for appsettings.Test.json
.ConfigureTestServices(ConfigureTestServices));
HttpClient = server.CreateClient();
_serviceProvider = server.Services;
Configuration = _serviceProvider.GetService<IConfiguration>();
}
protected T GetService<T>() where T : class
{
return _serviceProvider.GetService<T>();
}
protected virtual void ConfigureTestServices(IServiceCollection services)
{
// replace here whichever service you want for all tests
}
}
因此,在您的测试干净的所有样板之后,使用真正的实现(相同的 Startup),仅使用您指定的模拟(如果有)。
public class Given_Clients_In_Database_When_Getting_Filtered_Clients_By_Name
: FunctionalTest
{
private string _url;
private HttpResponseMessage _result;
protected override void ConfigureTestServices(IServiceCollection services)
{
base.ConfigureTestServices(services);
// replace here the service you use to read settings with mocks
// e.g: services.Replace(typeof(IConfiguration),...)
}
protected override async Task Given()
{
_url = "api/clients?name=foo";
}
protected override async Task When()
{
_result = await HttpClient.GetAsync(_url);
}
[Fact]
public void Then_It_Should_Return_200_Ok()
{
_result.StatusCode.Should().Be(HttpStatusCode.OK);
}
}
重要提示:以上配置允许覆盖已注册的服务、appsettings 文件等。还可以通过使用环境变量来覆盖特定的设置值。
只需设置任何环境变量,如Environment.SetEnvironmentVariable("Whichever__Whatever", "strongerPreferredValue");,appsettings.Test.json 或appsettings.json 中的Whichever:Whatever 设置就会被替换。
选项 B 看起来您可能希望在“真实”环境中运行这些测试,也许您不使用 TestServer。不过我建议不要这样做。
如果是烟雾测试或刺激测试,您需要真实的东西,没有假货。否则,它就不是真正的冒烟测试,因为它不能保证它对最终用户运行良好。
如果它是在受控环境下进行的某种集成测试,但像任何生产应用程序通常那样作为进程运行,我会考虑为这些事情提供一个单独的环境,您不必动态更改任何东西,你只需在哪里配置那个环境的设置。
如果您仍然认为最好运行应用程序并动态修改设置,那么您能做的最好的事情就是抽象出读取设置的功能,并确保您的应用程序中的所有内容都使用这种新的抽象来检索设置。
例如,您可以为IConfiguration 创建一个包装器
public interface IMySettings
{
object GetSettings(string name);
}
并且实现可以是
public class MySettings : IMySettings
{
private readonly IConfiguration _configuration;
public MySettings(IConfiguration configuration)
{
_configuration = configuration;
}
public object GetSettings(string name)
{
//Here your logic to return the real setting value as per environment, environment variable or appsettings. Or a custom one.
// or delegate in the IConfiguration as normal
return _configuration.GetValue<object>("Whichever:Whatever");
}
}
不要忘记注册services.AddTransient<IMySettings, MySettings>(); 并将IMySettings 注入您需要访问设置的任何控制器或服务中。
PS:对于 OptionA 中的测试方法,我有一个视频(西班牙语,抱歉),通过该方法。 https://www.youtube.com/watch?v=dyVEayGwU3I
还有一个代码示例位于https://gitlab.com/diegosasw/arquitectura-software/-/tree/master/src/testing