I have an integration tests project that uses .UseSetting()
in the test class, as follows:
public AccessTokenRetrieval() : base(nameof(AccessTokenRetrieval))
{
var connectionString = GetConnectionString();
var dbSettings = new DbSettings(connectionString);
_userGroupRepository = new UserGroupRepository(dbSettings);
_roleRepository = new RoleRepository(dbSettings);
_userRepository = new UserRepository(dbSettings);
_server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>()
.UseEnvironment("IntegrationTest")
.UseSetting("IntegrationTestConnString", dbSettings.IdentityServerConnectionString));
_handler = _server.CreateHandler();
_client = _server.CreateClient();
}
I would now like to retrieve that setting in the Startup.cs of my actual project. I attempted to do so using:
public void ConfigureIntegrationTestServices(IServiceCollection services)
{
var connectionString = Configuration.GetValue<string>("IntegrationTestConnString");
BuildIdentityServerTests(services, connectionString);
AddCoreServices(services, connectionString);
}
but that seems to return null.
What is the proper way to retrieve this setting?
Per-test setting
To pass a setting into a particular TestHost
instance, you could use ConfigureServices
call and override default setting registration.
First of all, register your default DbSettings
instance in Startup.ConfigureServices
method. It's mandatory to use TryAll
call:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.TryAddSingleton(new DbSettings(...));
}
}
Register a mocked instance in WebHostBuilder.ConfigureServices
call:
_server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>()
.ConfigureServices(services =>
{
services.AddSingleton(new DbSettings(...));
}
);
When you try to resolve a DbSettings
in the application code, you will get a mocked instance. Because WebHostBuilder.ConfigureServices
is executed first and TryAdd
call prevents to register a default instance.
This hint allows to replace any DI dependency you registered.
Global setting
To set an invariant (accross all tests) setting, set an process-scoped environment variable instead of UseSetting
call:
Environment.SetEnvironmentVariable("foo", "bar");
var _server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>()
);
Then read it:
public void ConfigureServices(IServiceCollection services)
{
string setting1 = Configuration["foo"];
string setting2 = Environment.GetEnvironmentVariable("foo");
}
You need to add environment variable provider to read variables from Configuration
:
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
//this config provider is mandatory to read env vars from Configuration
.AddEnvironmentVariables();
Configuration = builder.Build();
}
You could just inject the IConfigurationRoot where you could just use a local config file in the test project. You'd lose the default environment-specific config overrides that are the default in the new project template, but I personally don't use those for configuration management anyway (preferring non-committed appsetting.local.json instead which can be unique to developers and to the CI server).
var configuration = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile("appsettings.local.json", optional: true, reloadOnChange: true)
.AddInMemoryCollection(new Dictionary<string, string>
{
{"myConfig:setting1", "value1"},
{"myConfig:setting2", "value2"}
})
.Build();
var server = new TestServer(new WebHostBuilder()
.UseUrls("http://localhost:5001")
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.ConfigureServices(services => services.AddSingleton(configuration)));
And for Startup:
public class Startup
{
public Startup(IConfigurationRoot configuration)
{
this.Configuration = configuration;
}
...
}
Edit:
While this does work, it also seems to break IOptionsSnapshot
configuration injections from recognizing config file changes at run-time. I'm going to leave this answer, but will also keep digging for better way without injecting a custom service just to support test/fixture-specific config overrides.