在测试期间使用假 IDbContextFactory 注入数据进行测试

问题描述 投票:0回答:1
我正在使用 EFCore 6.0.16 InMemory 数据库提供程序来设置一些测试,但在尝试添加到表后在作用域上下文上执行

context.SaveChanges()

 时遇到空引用问题。

有一个 xUnit 固定装置可帮助为每个测试类创建数据库提供程序,并包含使用

InMemoryDatabase

 为我的数据库上下文调用 IDbContextFactory 的假实现的方法。

public class FakeDbContextFactory : IDbContextFactory<MyContext> { private readonly DbContextOptions<MyContext> _options; public FakeDbContextFactory(string name = "FakeTests") { _options = new DbContextOptionsBuilder<IPSContext>() .UseInMemoryDatabase(name) .Options; } public MyContext CreateDbContext() { return new MyContext(_options); } public Task<MyContext> CreateDbContextAsync() { return Task.FromResult(new MyContext(_options)); } } public class MyFixture : IDisposable { public MyFixture() { } public IDbContextFactory<MyContext> CreateFakeDbContext(string databaseName) { return new FakeDbContextFactory(databaseName); } }
我的测试对 xml 进行签名,返回一个散列值并将其与写入表中的预定值进行比较,但此示例是该示例的简化形式。我想检查签名哈希是否等于插入到内存提供程序中的值是否等于计算值。

public class HelperTests { private readonly MyFixture _fixture; private readonly IDbContextFactory<MyContext> _fakeDbContextFactory; public SignatureHelperTests(MyFixture fixture) { _fixture = fixture; _fakeDbContextFactory = _fixture.CreateFakeDbContext(nameof(HelperTests)); } [Fact] public void Test_1() { // Arrange var table = new MyTable { CreationDateTime = DateTime.Now, Id = "1234", SignedHash = "HashValue" }; using (var context = _fakeDbContextFactory.CreateDbContext()) { context.MyTables.Add(table); context.SaveChanges(); // Error is thrown here // Act var sut = new MyHelper(_fakeDbContextFactory); var signature = sut.GetValue("<xml>"); // Assert Assert.Equal(context.MyTables.Single().SignedHash, signature.SignedHash); } } }
堆栈跟踪如下:

System.NullReferenceException : Object reference not set to an instance of an object. Stack Trace:  You may need to build and run this test to see links to source files in the stack trace. lambda_method304(Closure , String ) <>c__DisplayClass4_0`2.<SanitizeConverter>b__1(Object v) InMemoryTable`1.SnapshotValue(IProperty property, ValueComparer comparer, IUpdateEntry entry) InMemoryTable`1.Create(IUpdateEntry entry) InMemoryStore.ExecuteTransaction(IList`1 entries, IDiagnosticsLogger`1 updateLogger) InMemoryDatabase.SaveChanges(IList`1 entries) StateManager.SaveChanges(IList`1 entriesToSave) StateManager.SaveChanges(StateManager stateManager, Boolean acceptAllChangesOnSuccess) <>c.<SaveChanges>b__104_0(DbContext _, ValueTuple`2 t) NonRetryingExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded) StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess) DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess) DbContext.SaveChanges()
我最初在 

using

 语句之外有 Act 和 Assert 部分,用于确定上下文已被处理,但意识到这并不重要,因为我的假每次都会返回一个新的上下文,并且固定装置应该返回一个新工厂并在内存提供程序中测试。

c# unit-testing entity-framework-core xunit
1个回答
0
投票
在 CreateDbContext 中,确保创建数据库:

public MyContext CreateDbContext() { var context = new MyContext(_options); context.Database.EnsureDeleted(); context.Database.EnsureCreated(); return context; }
请参阅:

https://learn.microsoft.com/en-us/ef/core/testing/testing-without-the-database#inmemory-provider

请注意,内存数据库可以说是用于单元测试的最差选择。 SQLLite 是一个更好的选择,但仍然可能存在问题。通常,测试的最佳选择是使用存储库模式(而不是通用存储库)作为单元测试的模拟边界。这模拟了存储库调用,而不是 DbContext 或内存中链接的 DbContext。

内存数据库的问题在于,它不会复制实际数据库的大部分行为,这可能会导致错误或与实际数据库不同的结果/行为。 SQLLite 的行为确实像数据库,但它仍然代表不同的提供程序,其行为与您实际选择的数据库提供程序不同。

© www.soinside.com 2019 - 2024. All rights reserved.