简体   繁体   中英

"Could not find a constructor that would match given arguments" returned when injecting a dbContext

Context

.NET 5 console application using the IHostedService pattern and EntityFramework Core 5.

Issue

The dbContext looks like this:

    public class WeatherDbContext : DbContext, IWeatherDbContext
    {
        public WeatherDbContext(DbContextOptions<WeatherDbContext> options) : base(options)
        {
        }

       public virtual DbSet<Observation> Observations { get; set; }
}

The hostbuilder is configured thus:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureLogging(logging =>
        {
            logging.ClearProviders();
        })
        .UseSerilog((hostContext, loggerConfiguration) =>
        {
            logConfiguration.WriteTo.File(ConfigurationManager.AppSettings["LogFile"]);
        })
        .ConfigureServices((services) =>
        {
            services.AddHttpClient()
                    .AddSingleton<CommandLineArguments>(new CommandLineArguments(args))
                    .AddSingleton<StringWriter>()
                    .AddDbContext<IWeatherDbContext, WeatherDbContext>(options =>
                    {
                        options.UseSqlServer(ConfigurationManager.ConnectionStrings["WeatherManagerDatabase"].ConnectionString);
                    })   
                    .AddTransient<IWeatherUndergroundAPIService(x => new WeatherUndergroundAPIService(ConfigurationManager.AppSettings["StationId"],
                                                                                                      ConfigurationManager.AppSettings["WUApiKey"],
                                                                                                      x.GetRequiredService<IHttpClientFactory>()))
                   .AddHostedService<DataDownloader>();                                                                                        
        });

...and the host service is constructed thus:

private readonly int importDayLimit;
private readonly ILogger logger;
private readonly StringWriter outputWriter;
private readonly int throttleLimit = 100;
private readonly IWeatherDbContext weatherDbContext;
private readonly IWeatherUndergroundAPIService wuApiService;
private DateTime FetchUpToDate;
private DateTime MostRecentlyRecordedObservationDate;

public DataDownloader(IWeatherUndergroundAPIService wuApiService,
                      ILogger logger,
                      IWeatherDbContext weatherDbContext,
                      StringWriter outputWriter,
                      CommandLineArguments commandLineArguments)
{
    this.wuApiService = wuApiService;
    this.weatherDbContext = weatherDbContext;
    this.logger = logger;
    this.outputWriter = outputWriter;
    this.importDayLimit = this.ProcessCommandLineArguments(commandLineArguments.Args);
}

I then have an XUnit test like this:

public class CommandLineArgumentValidation
{
    [Fact]
    public async Task CommandLineArgumentNotAnIntegerAsync()
    {
        // Arrange

        Mock<IWeatherUndergroundAPIService> mockWeatherUndergroundAPIService = new();

        DbContextOptions<WeatherDbContext> dbContextOptions = new DbContextOptionsBuilder<WeatherDbContext>()
            .UseInMemoryDatabase(databaseName: "testDb")
            .EnableDetailedErrors()
            .Options;

        IWeatherDbContext weatherDbContext = new WeatherDbContext(dbContextOptions);

        Mock<ILogger> mockLogger = new();
        
        StringBuilder consoleOutput = new();
        StringWriter consoleWriter = new(consoleOutput);
        
        CommandLineArguments commandLineArguments = new(new string[] { "not a positive integer" });

        DataDownloader dataDownloader = new(mockWeatherUndergroundAPIService.Object,
                                           mockLogger.Object,
                                           weatherDbContext,
                                           consoleWriter,
                                           commandLineArguments);
        
        CancellationToken cancellationToken = new(false);

        // Action

        await dataDownloader.StartAsync(cancellationToken);

        // Assertion

        Assert.Equal("Command line argument 'not a positive integer' is not a positive integer. Aborting download.", consoleOutput.ToString());
    }
}

The test throws an exception

Castle.DynamicProxy.InvalidProxyConstructorArgumentsException: Can not instantiate proxy of class: WeatherManagerDataDownloader.WeatherDbContext. Could not find a constructor that would match given arguments:

Note that I've simplified the code for clarity. I am mocking other services that get injected to DataDownloader but I'm not mocking the dBContext. EDIT: I've now added in the full code as it was noted that mocking appears to be occuring even though my simplified code didn't suggest that.

Question

Why is this test exception occuring? As I see it mocking should have nothing to do with the dBContext passed in.

I think this may be the answer and certainly it's allowing me to move forward.

When running the test with debugger I realise that everything is being injected successfully and the tested code is proceeding successfully. However I'm testing an invalid command line parameter situation and in DataDownloader I've coded

Environment.Exit(1)

to stop the app after outputing appropriate messages. Clearly that's not something you should do in an IHostedService and removing it makes the mocking error go away. Why it should cause a mocking error for something that hasn't been mocked remains baffling however.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM