我正在使用EF Core 2.2.4,并试图确定在单元测试中EF Core将哪些SQL语句发送到了SQLite数据库。由于我们使用的是xUnit(2.4.1),因此我们必须将日志消息写入ITestOutputHelper
实例,而xUnit注入的是测试类,而不是控制台。对于控制台,我找到了以下代码:
private static ILoggerFactory GetLoggerFactory()
{
IServiceCollection serviceCollection = new ServiceCollection();
serviceCollection.AddLogging(builder =>
builder.AddConsole()
.AddFilter(DbLoggerCategory.Database.Command.Name,
LogLevel.Information));
return serviceCollection.BuildServiceProvider()
.GetService<ILoggerFactory>();
}
我必须怎么做才能将此输出重定向到
ITestOutputHelper.WriteLine()
? 最佳答案
首先,创建一些样板日志记录代码以允许输出到ITestOutputHelper:
class TestLoggerProvider : ILoggerProvider
{
ITestOutputHelper _output;
public TestLoggerProvider(ITestOutputHelper output)
=> _output = output;
public ILogger CreateLogger(string categoryName)
=> new TestLogger(categoryName, _output);
public void Dispose()
{
}
}
class TestLogger : ILogger
{
string _categoryName;
ITestOutputHelper _output;
public TestLogger(string categoryName, ITestOutputHelper output)
{
_categoryName = categoryName;
_output = output;
}
public bool IsEnabled(LogLevel logLevel)
// NB: Only logging things related to commands, but you can easily expand
// this
=> _categoryName == DbLoggerCategory.Database.Command.Name;
public void Log<TState>(
LogLevel logLevel,
EventId eventId,
TState state,
Exception exception,
Func<TState, Exception, string> formatter)
{
// TODO: Customize the formatting even more if you want
//if (eventId == RelationalEventId.CommandExecuting)
//{
// var structure = (IReadOnlyList<KeyValuePair<string, object>>)state;
// var parameters = (string)structure.First(i => i.Key == "parameters")
// .Value;
// var commandText = (string)structure.First(i => i.Key == "commandText")
// .Value;
//}
_output.WriteLine(formatter(state, exception));
}
public IDisposable BeginScope<TState>(TState state)
=> null;
}
接下来,确保您的DbContext可以接受外部选项。
class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options)
: base(options)
{
}
}
最后,将其连接起来。这是为每个测试创建新上下文的示例。使用class或collection fixture可以延长上下文的生存期。
public class UnitTest1 : IDisposable
{
IServiceProvider _serviceProvider;
MyDbContext _db;
public UnitTest1(ITestOutputHelper output)
{
_serviceProvider = new ServiceCollection()
.AddLogging(x => x.AddProvider(new TestLoggerProvider(output)))
.AddEntityFrameworkSqlite()
.BuildServiceProvider();
_db = new MyDbContext(
new DbContextOptionsBuilder<MyDbContext>()
// Don't call UseLoggerFactory! (a new service provider would be
// created every time without ever getting disposed)
.UseInternalServiceProvider(_serviceProvider)
.UseSqlite("Data Source=:memory:")
.Options);
}
[Fact]
public void Test1()
{
_db.Database.ExecuteSqlRaw("-- Can you see me?");
}
public void Dispose()
{
_db.Dispose();
(_serviceProvider as IDisposable)?.Dispose();
}
}