如何在实体框架核心中对事务进行单元测试?



我有一个在事务中做一些工作的方法:

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;
}

我想添加单元测试来检查如果_context.SaveChangesAsync_otherRepository.Execute失败,事务是否回滚,这可能吗?

我看不到使用 InMemory 或 SQLite 的方法?

@Ilya Chumakov的出色回答使我能够对交易进行单元测试。 我们在评论中的讨论随后暴露了一些有趣的观点,我认为这些观点值得进入答案,这样它们就会更持久,更容易看到:

主要的一点是,实体框架记录的事件根据数据库提供程序而更改,这让我感到惊讶。 如果使用 InMemory 提供程序,则只会得到一个事件:

  1. 编号:1; 已执行命令

而如果您将 Sqlite 用于内存数据库,则会得到四个事件:

  1. 编号:1;已执行命令
  2. 编号:5;开始交易
  3. 编号:1;已执行命令
  4. 编号:6;提交事务

我没想到记录的事件会根据数据库提供程序而变化。

对于任何想要进一步研究的人,我通过更改 Ylya的日志记录代码来捕获事件详细信息,如下所示:

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; }
}

然后,我调整了返回内存中数据库的代码,以便我可以切换内存中数据库提供程序,如下所示:

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;
}
}

最后,在我的单元测试中,我确保在测试的断言部分之前清除事件日志,以确保我不会由于在测试的安排部分记录的事件而获得误报:

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);

可以检查 EF Core 日志中是否有RelationalEventId.RollingbackTransaction事件类型。我在这里提供了完整的详细信息:

如何跟踪实体框架核心事件以进行集成测试?

它的外观:

Assert.True(eventList.Contains((int)RelationalEventId.CommittingTransaction));

我想您是在询问提交失败时如何回滚,如果任何语句失败,EF core 将自动回滚 在此处阅读更多内容 ,如果您要求其他原因,或者您想在回滚发生时做某事,只是为了添加 try catch 块,

using (var transaction = await 
_context.Database.BeginTransactionAsync()){
try { 
_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();
} 
catch (Exception) 
{ 
// failed, Do something 
} }

相关内容

  • 没有找到相关文章

最新更新