让 EF Core 输出 SQL 语句到 xUnit 的 ITestOutputHelper
Getting EF Core to output SQL statements to xUnit's ITestOutputHelper
我正在使用 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 or 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();
}
}
我正在使用 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 or 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();
}
}