我正在使用EF Core 2.2.4,并试图找出EF Core在我们的单元测试中向我们的SQLite数据库发送哪些SQL语句。由于我们使用的是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)
{
}
}
最后,将其全部连接起来。这里是一个为每个测试创建一个新上下文的例子。使用一个 阶层 或 采集器 以延长上下文的寿命。
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();
}
}