Integration test with IOptions<> in .NET Core

2019-01-23 18:08发布

问题:

I pass IOption<T> to my CommandBus so I can get the settings from my ServiceBusSetting class. I want to do an integration test of my Bus. I do not want to resolve it just use new QueueCommandBus and need to pass IOptions to it.

var services = new ServiceCollection().AddOptions();
        services.Configure<ServiceBusAppSettings>(Configuration.GetSection("ServiceBus"));
        var options = services.BuildServiceProvider().GetService<IOptions<ServiceBusAppSettings>>();

        ////Act
        var commandBus = new QueueCommandBus(options);

This works fine, but feels very complex code to get the IOptions<T> from my appsetting.json in my test project.

Any clue if this is the only way or is there a better way?

回答1:

You don't need to create the ServiceCollection or IServiceProvider. The IConfiguration interface has a Bind() method, or from .NET Core 1.1 onwards, Get<T> which you can use to get the strongly-typed object directly:

var config = Configuration.GetSection("ServiceBus");

// .NET Core 1.0
var options = new ServiceBusAppSettings();
config.Bind(options);

// .NET Core 1.1
var options = config.Get<ServiceBusAppSettings>();

I like to add these as static methods to my AppSettings strongly-typed object, to make it convenient to load them from JSON in both my web app and from unit tests.

AppSettings.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;

namespace My.Namespace
{
    public class AppSettings
    {
        public class ServiceBusAppSettings
        {
            public string Setting1;
            public int Setting2;
        }

        public class ApiSettings
        {
            public bool FormatJson { get; set; }
        }

        public class MySqlSettings
        {
            public string User { get; set; }
            public string Password { get; set; }
            public string Host { get; set; }
            public string Database { get; set; }
            public int Port { get; set; } = 3306;

            public string GetConnectionString()
            {
                return $"Server={Host};Database={Database};Port={Port};Uid={User};Pwd={Password}";
            }

        }

        public ServiceBusAppSettings ServiceBus { get; set; } = new ServiceBusAppSettings();
        public ApiSettings Api { get; set; } = new ApiSettings();
        public MySqlSettings MySql { get; set; } = new MySqlSettings();

        // Static load helper methods. These could also be moved to a factory class.
        public static IConfigurationRoot GetConfiguration(string dir)
        {
            return GetConfiguration(dir, null);
        }

        public static IConfigurationRoot GetConfiguration(string dir, string environmentName)
        {
            if (string.IsNullOrEmpty(environmentName))
                environmentName = "Development";

            var builder = new ConfigurationBuilder()
                .SetBasePath(dir)
                .AddJsonFile("appsettings.json", true, true)
                .AddJsonFile($"appsettings.{environmentName}.json", true)
                .AddEnvironmentVariables();

            return builder.Build();
        }

        public static AppSettings GetSettings(string dir)
        {
            return GetSettings(dir, null);
        }

        public static AppSettings GetSettings(string dir, string environmentName)
        {
            var config = GetConfiguration(dir, environmentName);
            return GetSettings(config);
        }

        public static AppSettings GetSettings(IConfiguration config)
        {
            return config.Get<AppSettings>();
        }
    }
}

ASP.NET Core Startup.cs: (Getting the strongly-typed settings object is often helpful at this stage, when configuring the other services...)

public class Startup
{
    public Startup(IHostingEnvironment env)
    {
        Configuration = AppSettings.GetConfiguration(env.ContentRootPath, env.EnvironmentName);
    }

    public IConfigurationRoot Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        // Configure the service collection.
        services.AddOptions();
        services.Configure<AppSettings>(Configuration);

        // It can also be handy to get the AppSettings object here.
        var settings = AppSettings.GetSettings(Configuration);

        // Add framework services.
        services.AddMvc()
            .AddJsonOptions(options =>
            {
                options.SerializerSettings.ContractResolver = new DefaultContractResolver();
                // Pretty-print JSON in Development
                options.SerializerSettings.Formatting = settings.Api.FormatJson ? Formatting.Indented : Formatting.None;
            });

        // Store DB connection info in AppSettings too...
        var conn = settings.MySql.GetConnectionString();
        services.AddDbContext<MyDbContext>(opt => opt.UseMySql(conn));
    }
}

In Test Class:

var testDir = AppContext.BaseDirectory;
var settings = AppSettings.GetSettings(testDir, "Test");

//Act
var commandBus = new QueueCommandBus(settings);