1

I took a legacy .NET framework application, that consisted of class libraries and a unit test project only, and converted them all to .NET core 3.1.

I can't seem to execute or bootstrap the unit tests with the .NET core method of using the IHostBuilder's CreateHostBuilder.

When I debug any of my unit tests, it stops executing and I get this error:

The following constructor parameters did not have matching fixture data: IOptions`1 appConfig, IServiceX serviceX, ITemplateEngine templateEngine Exception doesn't have a stacktrace

Sample unit test:

public class TemplateGenerationTests : BaseTest
{
    private readonly AppConfiguration appConfig;
    private readonly IServiceX serviceX;
    private readonly ITemplateEngine templateEngine;

    public TemplateGenerationTests(IOptions<AppConfiguration> appConfig, IServiceX serviceX, ITemplateEngine templateEngine)
    {
        this.appConfig = appConfig.Value;
        this.serviceX = serviceX;
        this.templateEngine = templateEngine;
    }

    [Fact]
    public void SomeTestIhave()
    {
        //removed for brevity
        serviceX.DoWork(appConfig.SomeAppProp);
        

        Assert.NotNull(result);
        Assert.NotEmpty(result);
    }
}

Base class (I have a feeling this is the incorrect way of creating the hostbuiler):

public class BaseTest
{
    public BaseTest()
    {
        var builder = CreateHostBuilder();
        Task.Run(() => builder.RunConsoleAsync());
    }

    public static IHostBuilder CreateHostBuilder(string[] args = null) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                config.AddJsonFile("appsettings.json", optional: true);
                config.AddEnvironmentVariables();

                if (args != null)
                {
                    config.AddCommandLine(args);
                }
            })
            .ConfigureServices((hostContext, services) =>
            {
                services.AddOptions();
                services.Configure<AppConfiguration>(hostContext.Configuration.GetSection("AppConfiguration"));
                
                services.AddTransient<IServiceX, ServiceX>();
                services.AddTransient<ITemplateEngine, TemplateEngine>();
                
                //removed for brevity..
            })
            .ConfigureLogging((hostingContext, logging) =>
            {
                logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
                logging.AddConsole();
            });
}

Ideally, I am looking for a single point where I can setup the HostBuilder i.e. in a console or API .NET project, this would be in the Main method of the Program class.

2 Answers 2

7

With the small modification you can get rid of injecting services into TemplateGenerationTests class:

The BaseTest class:

public class BaseTest
{
    public BaseTest()
    {
        TestHost = CreateHostBuilder().Build();
        Task.Run(() => TestHost.RunAsync());
    }

    public IHost TestHost { get; }

    public static IHostBuilder CreateHostBuilder(string[] args = null) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                config.AddJsonFile("appsettings.json", optional: true);
                config.AddEnvironmentVariables();

                if (args != null)
                {
                    config.AddCommandLine(args);
                }
            })
            .ConfigureServices((hostContext, services) =>
            {
                services.AddOptions();
                services.Configure<AppConfiguration>(hostContext.Configuration.GetSection("AppConfiguration"));

                services.AddTransient<IServiceX, ServiceX>();
                services.AddTransient<ITemplateEngine, TemplateEngine>();

                //removed for brevity..
            })
            .ConfigureLogging((hostingContext, logging) =>
            {
                logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
                logging.AddConsole();
            });
}

And the TemplateGenerationTests class:

public class TemplateGenerationTests : IClassFixture<BaseTest>
{
    private readonly IHost _host;

    public TemplateGenerationTests(BaseTest baseTest)
    {
        _host = baseTest.TestHost;
    }

    [Fact]
    public void SomeTestIhave()
    {
        // Arrange
        var serviceX = _host.Services.GetService<IServiceX>();
        var appConfig = _host.Services.GetService<AppConfiguration>();

        // Act
        var result = serviceX.DoWork(appConfig.SomeAppProp);

        // Assert
        Assert.NotNull(serviceX);
        Assert.NotEmpty(result);
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

This is working except for one part, the appConfig is not resolving and it is null.
Check the result of this line is not null hostContext.Configuration.GetSection("AppConfiguration")
Yes, that was returning a null value. Thanks
0

No need to copy the CreateHostBuilder method from Program.cs (DRY). Just set its visibility to public (or internal using [assembly: InternalsVisibleToAttribute("Your.Test.Project")] ) and create a little Helper class in Your.Test.Project:

// using Microsoft.Extensions.DependencyInjection;
// using Microsoft.Extensions.Hosting;
public static T GetRequiredService<T>()
{
    IHost testHost = Program.CreateHostBuilder().Build();
    return testHost.Services.GetRequiredService<T>();
}

Your test class:

public class TemplateGenerationTests
{
    private IServiceX serviceX;
    private AppConfiguration appConfig;

    public TemplateGenerationTests()
    {
        serviceX = Helper.GetRequiredService<IServiceX>();
        appConfig = Helper.GetRequiredService<AppConfiguration>();
    }

    [Fact]
    public void SomeTestIhave()
    {
        // Act
        var result = serviceX.DoWork(appConfig.SomeAppProp);

        // Assert
        Assert.NotNull(serviceX);
        Assert.NotEmpty(result);
}

}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.