We need to ensure that EF Core-based code has performed a specific kind of database-level operation under test (for example, any command execution or any transaction commit).
Let's suppose a real database should be triggered and we can't isolate it by DbContext
mocking. How could it look:
[Fact]
public async Task Test()
{
using (var context = new FooContext())
{
//context-related code to produce queries and commands
}
Assert.True(/* any context-related transaction has been committed */);
}
Is it possible?
EF Core doesn't provide its own tracing mechanism. However, it logs a lot of database interaction events. We could gather these log messages and check their EventId
to determine did a specific operation occur or not. Here is the list of relational events used by EF Core:
EF Core 1.1.2: RelationalEventId enum.
EF Core 2.0.0 preview 1: RelationalEventId class (breaking change!).
All we need to do is create a fake logger and pass it to context:
[Fact]
public async Task TransactionCommit_Logger_ContainsEvent()
{
var logger = new FakeLogger();
var factoryMock = new Mock<ILoggerFactory>();
factoryMock.Setup(f => f.CreateLogger(It.IsAny<string>()))
.Returns(logger);
using (var context = new FooContext(factoryMock.Object))
{
using (var transaction = await context.Database.BeginTransactionAsync())
{
transaction.Commit();
}
}
Assert.True(logger.Events.Contains((int)RelationalEventId.CommittingTransaction));
}
FakeLogger
adds a logged event id to the Events
list.
public class FakeLogger : ILogger
{
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception,
Func<TState, Exception, string> formatter)
{
Events.Add(eventId.Id);
}
public List<int> Events { get; set; } = new List<int>();
public bool IsEnabled(LogLevel logLevel) => true;
public IDisposable BeginScope<TState>(TState state) => null;
}
Call UseLoggerFactory
to attach a factory instance to a context:
public class FooContext : FooParentContext
{
private readonly ILoggerFactory _loggerFactory;
public FooContext() { }
public FooContext(ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseLoggerFactory(_loggerFactory);
}
}
P.S. You could go even deeper and analyze log message or even raw SQL produced by EF.