C# EF - 如何对内存中的 SQLite 文件进行单元测试?

问题描述 投票:0回答:1

在我的单元测试中,我当前正在制作 SQLite 文件的临时副本,运行我正在测试的方法,然后删除该文件。

我遇到的问题是,当我使用同一文件进行多个单元测试时,测试要么全部通过,要么只有其中一个测试失败并出现错误

System.IO.IOException : The process cannot access the file 'C:\~\temp_飲newKanji_食欠人良resourceKanji_decks.anki2' because it is being used by another process.
。我什至连续运行每个测试 20 次,以确保它不是随机地自行失败,而且它们都通过了。

我遇到了类似的问题,在尝试删除临时文件后会出现此错误,并且测试总是失败,即使是单独测试。事实证明,问题是 EF 一直保持连接,即使在我处理它之后也是如此,我可以通过在

SqliteConnection.ClearPool()
之前调用
_context.Dispose()
来修复它。

堆栈跟踪显示它在删除临时文件行仍然失败,但我不确定还有什么可能导致它。此外,也许第一个单元测试是如何保持文件打开的,但如果是这种情况,当它尝试在自己的单元测试中删除文件时,它也应该失败。

我能找到的唯一替代方法是将文件复制到内存中,这不应该使任何文件保持打开状态,但我似乎不知道如何实现这一点。我所看到的有关内存中 SQLite 的所有内容都有

new DbContextOptionsBuilder<YourDbContext>().UseSqlite("DataSource=:memory:").Options
,然后手动添加所有数据,但我找不到任何有关如何使用现有 SQLite 文件中的数据进行设置的示例。我该怎么做呢?

我正在使用 C#、.NET 8、EF Core 8 Sqlite 和 xUnit。

public class Anki2Context : DbContext, IAnki2Context
{
    public DbSet<Card> Cards { get; protected set; }
    public DbSet<Deck> Decks { get; protected set; }
    public DbSet<Note> Notes { get; protected set; }

    public Anki2Context(string dbPath) : base(GetOptions(dbPath))
    {
    }

    public Anki2Context(DbContextOptions<Anki2Context> options) : base(options)
    {
    }

    private static DbContextOptions<Anki2Context> GetOptions(string dbPath)
    {
        return new DbContextOptionsBuilder<Anki2Context>()
            .UseSqlite($"Data Source={dbPath}")
            .Options;
    }
}
public class Anki2Controller : IDisposable
{
    private readonly Anki2Context _context;

    public Anki2Controller(Anki2Context context)
    {
        _context = context;
    }

    public Anki2Controller(string dbPath) : this(new Anki2Context(dbPath))
    {
    }

    public void Dispose()
    {
        SqliteConnection.ClearPool((SqliteConnection) _context.Database.GetDbConnection());
        _context.Dispose();
    }
}
[Theory]
[InlineData("飲newKanji_食欠人良resourceKanji_decks.anki2", new[] { 1707169497960, 1707169570657, 1707169983389, 1707170000793 }, 1707160682667)]
public void Move_notes_between_decks(string anki2File, long[] noteIdsToMove, long deckIdToMoveTo)
{
    //Arrange
    string originalInputFilePath = _anki2FolderPath + anki2File;
    string tempInputFilePath = _anki2FolderPath + "temp_" + anki2File;
    File.Copy(originalInputFilePath, tempInputFilePath, true);//Copy the input file to prevent changes between unit tests
    Anki2Controller anki2Controller = new Anki2Controller(tempInputFilePath);
    List<Card> originalNoteDeckJunctions = anki2Controller.GetTable<Card>()
                                                        .Where(c => noteIdsToMove.Contains(c.NoteId))
                                                        .ToList();//Grab the current note/deck relations for the give note ids

    //Act
    bool movedNotes = anki2Controller.MoveNotesBetweenDecks(noteIdsToMove, deckIdToMoveTo);

    //Assert
    movedNotes.Should().BeTrue();//Function completed successfully
    List<Card> finalNoteDeckJunctions = anki2Controller.GetTable<Card>()
                                                        .Where(c => noteIdsToMove.Contains(c.NoteId))
                                                        .ToList();//Grab the current note/deck relations for the give note ids after running the function
    finalNoteDeckJunctions.Count().Should().Be(originalNoteDeckJunctions.Count());//No note/deck relations should have been removed/added
    finalNoteDeckJunctions.Select(c => c.DeckId).Should().AllBeEquivalentTo(deckIdToMoveTo);//All junction deckIds should be the given deckId

    //Cleanup
    anki2Controller.Dispose();
    File.Delete(tempInputFilePath);
}
c# sqlite entity-framework-core xunit in-memory-database
1个回答
0
投票

这是将 sqlite 从物理文件复制到内存中的方法:

创建Fixture类:

public class TestDatabaseFixture: IDisposable
{
    private static readonly object _lock = new();
    private static bool _databaseInitialized;
    private const string SourceFile = @"This is absolute path to Sqlite file";
    private readonly SqliteConnection? _inMemoryDbConnection;
    private readonly DbContextOptions<ApplicationDbContext>? _contextOptions;

    public TestDatabaseFixture()
    {
        lock (_lock)
        {
            if (!_databaseInitialized)
            {
                _inMemoryDbConnection = new SqliteConnection(String.Format("Data Source = {0}", ":memory:"));
                _inMemoryDbConnection.Open();
                using (SqliteConnection source = new SqliteConnection(String.Format("Data Source = {0}", SourceFile)))
                {
                    source.Open();
                    source.BackupDatabase(_inMemoryDbConnection);
                }

                _contextOptions = new DbContextOptionsBuilder<ApplicationDbContext>()
                    .UseSqlite(_inMemoryDbConnection)
                    .Options;

                _databaseInitialized = true;
            }
        }
    }
    public ApplicationDbContext CreateContext()
        => new ApplicationDbContext(_contextOptions);

    public void Dispose()
    {
        _inMemoryDbConnection.Close();
        _inMemoryDbConnection?.Dispose();
    }
}

然后您可以在测试中使用 DbContext:

public class UnitTests1: IClassFixture<TestDatabaseFixture>
{
    TestDatabaseFixture fixture;

    public UnitTests1(TestDatabaseFixture fixture)
    {

        this.fixture = fixture;
    }

    [Fact]
    public void Test1()
    {
        var context = fixture.CreateContext();
        using var viewCommand = context.Database.GetDbConnection().CreateCommand();
        viewCommand.CommandText = @"
            SELECT count(*) FROM Artist;
        ";

        var result = viewCommand.ExecuteScalar();

        Assert.IsType<long>(result);
        Assert.NotEqual(0, result);
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.