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
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.