Seany84
Seany84

Reputation: 5596

Where to create HostBuilder and avoid 'The following constructor parameters did not have matching fixture data'

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.

Upvotes: 1

Views: 6725

Answers (2)

Baccata
Baccata

Reputation: 581

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);
}

}

Upvotes: 0

Mohsen Esmailpour
Mohsen Esmailpour

Reputation: 11544

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);
    }
}

Upvotes: 7

Related Questions