How to load appsettings.Development.json from project within XUnit tests - c#

My tests use HttpClient. So I have built a helper method init the client in the constructor of all of my test files.
I initially tried using AppContext.BaseDirectory to get the base directory for the project that I was trying to test. The problem is that its not picking up the correct project name so its loading the appsettings.Development.json from a different project causing the tests to fail. I ended up hard coding the project dir.
I cant figure out how I can force it to load the appsettings.Development.json from the project I want to test.
public static HttpClient GetHttpClient()
{
// projectDir one, loads tests project.
// var projectDir = AppContext.BaseDirectory;
var projectDir = #"C:\Work\Src\Application\bin\Debug\net5.0";
var server = new TestServer(new WebHostBuilder()
.UseEnvironment("Development")
.UseConfiguration(new ConfigurationBuilder()
.SetBasePath(projectDir)
.AddJsonFile("appsettings.Development.json")
.Build()
)
.UseStartup<Startup>()
);
return server.CreateClient();
}
The main issue being that it cant load the settings for RabbitMQ which come from the appsettings.Development.json file.
// Load RabbitMQ config.
var serviceClientSettingsConfig = Configuration.GetSection("RabbitMq");
services.Configure<RabbitMqConfiguration>(serviceClientSettingsConfig);
Note: I plan to mock this latter but for now my client needs to see end to end tests working.

Related

Set Enviroment in WebApplication.CreateBuilder from configuration

So I'm trying to setup the webapp enviroment name according to the company deployment policy based on transformations of appsettings file during the pipeline deployment. We are using IWebHostEnvironment for reading the env and rootpath later in startup process.
But I'm running into the issue that I don't know how properly resolve. Is there any option to prebuild the configuration so that I can read value from it before creating new builder or this is the 'way' how to do it. To have one default pre-builder for configuration and than create regular one for the normal app.
It looks like chicken-egg problem to me.
Other solution would be to read 'environment' from the configuration directly but doesn't look clean to me.
var configBuilder = WebApplication.CreateBuilder(args);
configBuilder.Configuration.SetupConfiguration(args);
var section = configBuilder.Configuration.GetSection("Hosting")["Environment"];
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
EnvironmentName = section
});
By default environment comes from environment variables (see the docs). If you really need to read the environment from config file then the approach with WebApplicationOptions is the way to go. You can improve it a bit by just reading the config, not using WebApplication.CreateBuilder for that:
var cfgBuilder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(...); // setup your config probably call cfgBuilder.SetupConfiguration(args)
var cfg = cfgBuildeR.Build();
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
EnvironmentName = cfg.GetSection("Hosting")["Environment"]
});

How to use settings from integration testing project in the Startup class of api project?

I'm trying to write integration tests for web api, I have two appsettings.json files one for api project and other for integration tests. I want to use the values(azure storage connection strings) from integration testing project in Startup class of web api.
I have tried creating CustomWebApplicatonFactory it didn't work since the Startup class of web api gets settings like shown below.
//Configure services method in Startup class
public virtual void ConfigureServices(IServiceCollection services)
{
var settings = ConfigurationProvider.GetConfiguration();
services.TryAddSingleton(settings);
services.AddHttpClient();
var azureTableStorageConnectionString =
settings["AzureMainStoreConnectionStringSecretName"];
//Other Startup related code
}
I want to change the value of "azureTableStorageConnectionString" from my integration test project. Help and suggestions are much appreciated.
First of all, instead of using the static function ConfigurationProvider.GetConfiguration() inject IConfiguration into your Startup class. The host that is usually defined in the Program class builds that for you so you can inject it.
Then instead of using WebApplicationFactory<TStartup> you can build a test-host yourself like this.
[Fact]
public async Task LoadWeatherForecastAsync()
{
var webHostBuilder = new WebHostBuilder()
.UseContentRoot(AppContext.BaseDirectory)
.ConfigureAppConfiguration(builder =>
{
builder.Sources.Clear();
builder.SetBasePath(AppContext.BaseDirectory);
// this is an appsettings file located in the test-project!
builder.AddJsonFile("appsettings.Testing.json", false);
})
.UseStartup<Startup>();
var host = new TestServer(webHostBuilder);
var response = await host.CreateClient().GetAsync("weatherforecast");
Assert.True(response.IsSuccessStatusCode);
}
I've created a sample on github. You can clone it and try it out.

How to get all the Services from Startup.cs

How do I obtain all the services after StartupClass has run?
We have a Startup Configure Services class which
runs multiple dependency injection.
serviceCollection.AddScoped<IDepartmentRepository, DepartmentRepository>();
serviceCollection.AddScoped<IDepartmentAppService, DepartmentAppService>();
Team is using Xunit Test Project and wants to grab all the Service collection list for Xunit.
Need services from this below:
new WebHostBuilder()
.UseContentRoot("C:\\Test\\Test.WebAPI")
.UseEnvironment("Development")
.UseConfiguration(new ConfigurationBuilder()
.SetBasePath("C:\\Test\\Test.WebAPI")
.AddJsonFile("appsettings.json")
.Build()).UseStartup<Startup>());
Services in ServiceCollection are represented by the ServiceDescriptor class. You can get a list of all service descriptors by chaining a ConfigureTestServices method to your WebHostBuilder (you have to install Microsoft.AspNetCore.TestHost package)
using Microsoft.AspNetCore.TestHost;
....
IWebHost CreateWebHost(out ServiceDescriptor[] allServices)
{
ServiceDescriptor[] grabbedServices = null;
var host = new WebHostBuilder()
// ... other stuff ...
.UseStartup<Startup>()
// add the following call:
.ConfigureTestServices(services => {
// copy all service descriptors
grabbedServices = services.ToArray();
})
// build the host now to trigger the above chain
.Build();
allServices = grabbedServices;
return host;
}
It is then convenient to use like this:
[Fact]
public void TestSomething()
{
// arrange
var host = CreateWebHost(out var services);
// ... do something with the services
// act
// assert
}
The reason we have to use ConfigureTestServices (and not ConfigureServices) is that ConfigureServices is always invoked before the methods in the Startup class, whereas ConfigureTestServices is invoked afterward.
UPDATE
Of course, it's also possible to just store the reference to ServiceCollection in a variable/field, and enumerate the services whenever necessary.
So I understood your question that you want to test you services and have problems with loading them in test project.
I'd say you don't need to. In unit tests every time you test a single service. This service you instantiate directly:
var target= new ServiceToTest();
So you reference your testED project from your test project. In case your implementation is internal, add to your testED project an AssemblyInfo.cs file and put the following inside:
[assembly: InternalsVisibleTo("MyTestProject")]
Whenever you test your MyServiceToTest1, in case it has a dependecy to MyServiceTest2, you don't have to load or instantiate MyServiceTest2. Instead you mock it, e.g. with moq.

Routes in AspNetCore.TestHost depend on Startup.cs location?

I am trying to test my ASP.NET Core Web Application with Microsoft.AspNetCore.TestHost. It works fine this way (result has status 200):
var server = new TestServer(new WebHostBuilder().UseStartup<Startup>());
var client = server.CreateClient();
var result = await client.GetAsync(someRequestUrl);
In this case the real Startup class from the API project is used.
However, I don't want to use the real Startup class in my integration test. The main reason is the need to mock some stuff that gets wired during application startup. For example, the database server to be used. It can be done in a very elegant way by defining a virtual method in Startup.cs:
public virtual void SetupDbContext(IServiceCollection services)
{
services.AddDbContext<TbsDb>(options =>
options.UseSqlServer("someConnectionString"));
}
Then I create a new class, which inherits from the original Startup class and overrides this method to use Sqlite, in-memory database or whatever:
public override void SetupDbContext(IServiceCollection services)
{
services.AddDbContext<TbsDb>(
options => options.UseSqlite("someConnectionString"));
}
This also works well with TestHost if the new class is in the same API project.
Obviously, I don't want this class which is used for testing to be there. But if I move it to integration tests project and create a TestServer there, the same test fails because the result has status 404. Why is it happening? It still inherits from the Startup class, which is in the API project. Thus I expect all the routes to work the same no matter where the TestStartup class is. Can it be solved somehow?

App.config for Xunit

I'm writing some xUnit tests for some helper classes that relies on some configuration settings, usually stored in App.config or Web.config of the executing project.
The config looks like this:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="FileNamePattern" value="\\d{8}_\\w{4:20}\.png"/>
<!-- and the likes -->
</appSettings>
</configuration>
I'm running xUnit 1.9 with the GUI runner (xunit.gui.clr4.exe) and xUnit console runner (on the Jenkins CI server). Currently, I can "inject" these configuration values into the test environments by setting the xunit.gui.clr4.exe.config and xunit.console.exe.config files manually); however, this is tedious and error-prone.
I could also mock these configuration settings in a fixture. But using the same fixture across 10 different files is rather repetitive.
Is there a better way to mock these configuration settings with xUnit, such as providing a App.config file for the test project?
If your code assumes they are in the app.config, then xUnit.net supports having them wired up in there by providing one (typically when the tests are in a DLL file, this means you get a AssemblyName.dll.config file in the project outputs which the runner loads as the settings if it exists at load time).
Obviously no harm to use the DI principle to remove such dependencies in the first place, but I'd say don't go messing with code before you actually get it under test first.
To keep it DRY, put the app.config in a central place and add it as a link (via the arrow on the Open button in the dialog). (Yes, there's lots not to like about that - use only if you feel its the least evil approach.)
One thing to look out for is that changes don't get reloaded in the GUI runner unless you ask for the assembly to be reloaded.
From perspective more complex projects & team work, I recommend:
separate .config file for xUnit project (it takes advantage of independent configuration & logging for running tests)
set-up Dependency Injection together with .config reading for xUnit project alone
Our team is using this pattern of xUnit init & dispose:
public class MyTest : IDisposable
{
public IServiceProvider Services { get; private set; }
public MyProjectOptions Options { get; private set; }
public Logger Logger { get; private set; }
private void Configure()
{
// appsettings.workspace.json for custom developer configuration
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.AddJsonFile("appsettings.workspace.json", optional: true)
.Build();
Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.LiterateConsole()
.WriteTo.RollingFile("logs/{Date}-log.txt")
.CreateLogger();
Options = configuration.GetSection("MyProject").Get<MyProjectOptions>();
var services = new ServiceCollection();
services.AddSingleton<ILogger>(s => Logger);
// other DI logic and initializations ...
//services.AddTransient(x => ...);
Services = services.BuildServiceProvider();
}
public MyTest()
{
Configure();
// ... initialize data in the test database ...
var data = Services.GetService<TestDataService>();
data.Clean();
data.SeedData();
}
public void Dispose()
{
// ... clean-up data in the test database ...
var data = Services.GetService<TestDataService>();
data.Clean();
}
}
The solution for me was to name the config file testhost.dll.config, add it to the solution, and set its Copy to Output Directory setting to Copy always.
It has to be named testhost.dll.config because that's how the xUnit component that invokes my tests-project is named. It can be determined as follows:
string invoker = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).FilePath;
For my setup, invoker turned out to be testhost.dll.

Categories