tom*_*dox 5 sqlite unit-testing transactions in-memory-database entity-framework-core
我有一个在事务中做一些工作的方法:
public async Task<int> AddAsync(Item item)
{
int result;
using (var transaction = await _context.Database.BeginTransactionAsync())
{
_context.Add(item);
// Save the item so it has an ItemId
result = await _context.SaveChangesAsync();
// perform some actions using that new item's ItemId
_otherRepository.Execute(item.ItemId);
transaction.Commit();
}
return result;
}
Run Code Online (Sandbox Code Playgroud)
我想添加单元测试来检查如果_context.SaveChangesAsync或_otherRepository.Execute失败则事务会回滚,这可能吗?
@Ilya Chumakov 的出色回答使我能够对交易进行单元测试。我们在评论中的讨论暴露了一些有趣的观点,我认为这些观点值得进入答案,这样它们就会更持久、更容易看到:
主要的一点是实体框架记录的事件依赖于数据库提供者的变化,这让我感到惊讶。如果使用 InMemory 提供程序,您只会收到一个事件:
然而,如果您使用 Sqlite 作为内存数据库,您会收到四个事件:
我没想到记录的事件会根据数据库提供商的不同而改变。
对于任何想要进一步了解这一点的人,我通过更改 Ilya 的日志记录代码来捕获事件详细信息,如下所示:
public class FakeLogger : ILogger
{
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception,
Func<TState, Exception, string> formatter)
{
var record = new LogRecord
{
EventId = eventId.Id,
RelationalEventId = (RelationalEventId) eventId.Id,
Description = formatter(state, exception)
};
Events.Add(record);
}
public List<LogRecord> Events { get; set; } = new List<LogRecord>();
public bool IsEnabled(LogLevel logLevel) => true;
public IDisposable BeginScope<TState>(TState state) => null;
}
public class LogRecord
{
public EventId EventId { get; set; }
public RelationalEventId RelationalEventId { get; set; }
public string Description { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
然后我调整了返回内存数据库的代码,以便我可以按如下方式切换内存数据库提供程序:
public class InMemoryDatabase
{
public FakeLogger EfLogger { get; private set; }
public MyDbContext GetContextWithData(bool useSqlite = false)
{
EfLogger = new FakeLogger();
var factoryMock = Substitute.For<ILoggerFactory>();
factoryMock.CreateLogger(Arg.Any<string>()).Returns(EfLogger);
DbContextOptions<MyDbContext> options;
if (useSqlite)
{
// In-memory database only exists while the connection is open
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
options = new DbContextOptionsBuilder<MyDbContext>()
.UseSqlite(connection)
.UseLoggerFactory(factoryMock)
.Options;
}
else
{
options = new DbContextOptionsBuilder<MyDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
// don't raise the error warning us that the in memory db doesn't support transactions
.ConfigureWarnings(x => x.Ignore(InMemoryEventId.TransactionIgnoredWarning))
.UseLoggerFactory(factoryMock)
.Options;
}
var ctx = new MyDbContext(options);
if (useSqlite)
{
ctx.Database.EnsureCreated();
}
// code to populate the context with test data
ctx.SaveChanges();
return ctx;
}
}
Run Code Online (Sandbox Code Playgroud)
最后,在我的单元测试中,我确保在测试的断言部分之前清除事件日志,以确保我不会由于在测试的安排部分记录的事件而得到误报:
public async Task Commits_transaction()
{
using (var context = _inMemoryDatabase.GetContextWithData(useSqlite: true))
{
// Arrange
// code to set up date for test
// make sure none of our setup added the event we are testing for
_inMemoryDatabase.EfLogger.Events.Clear();
// Act
// Call the method that has the transaction;
// Assert
var result = _inMemoryDatabase.EfLogger.Events
.Any(x => x.EventId.Id == (int) RelationalEventId.CommittingTransaction);
Run Code Online (Sandbox Code Playgroud)
您可以检查 EF Core 日志中的RelationalEventId.RollingbackTransaction事件类型。我在这里提供了完整的详细信息:
如何跟踪 Entity Framework Core 事件以进行集成测试?
它看起来如何:
Assert.True(eventList.Contains((int)RelationalEventId.CommittingTransaction));
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
9139 次 |
| 最近记录: |