如何测试 DB.Configuration.AutoDetectChangesEnabled = false

How to test DB.Configuration.AutoDetectChangesEnabled = false

我正在尝试使用 NSubstitute 为 class 编写一些测试。

Class 构造函数是:

public class ClassToTest : IClassToTest
    {
        private IDataBase DB;
        public ClassToTest(IDatabase DB)
        {
            this.DB = DB;
            this.DB.Configuration.AutoDetectChangesEnabled = false;
        }

这是我的单元测试 class:

[TestFixture]
public class ClassToTestUnitTests
{
    private ClassToTest  _testClass;

    [SetUp]
    public void SetUp()
    {
        var Db = Substitute.For<IDatabase>();
        //Db.Configuration.AutoDetectChangesEnabled = false; <- I've tried to do it like this

        var dummyData = Substitute.For<DbSet<Data>, IQueryable<Data>, IDbAsyncEnumerable<Data>>().SetupData(GetData());                                         

        Db.Data.Returns(dummyData);

        _testClass = new ClassToTest(Db);      
    }

每当我尝试测试某些方法时,测试都会失败并且会出现 NullReferenceException,它会在 StackTrace 中转到 SetUp 方法。

当我注释掉 this.DB.Configuration.AutoDetectChangesEnabled = false;ClassToTest 构造函数中测试工作正常。

编辑:

 public interface IInventoryDatabase
    {
        DbSet<NamesV> NamesV { get; set; }
        DbSet<Adress> Adresses  { get; set; }
        DbSet<RandomData> Randomdata { get; set; }
             // (...more DbSets) 

        System.Data.Entity.Database Database { get; }
        DbContextConfiguration Configuration { get; }
        int SaveChanges();
   }

NullReferenceException的原因是NSubstitute不能自动替换DbContextConfiguration(它只能自动替换purely virtual classes)。

通常我们可以通过手动配置这个 属性 来解决这个问题,比如 Db.Configuration.Returns(myConfiguration),但在这种情况下 DbContextConfiguration 似乎没有 public 构造函数所以我们无法为 myConfiguration.

创建实例

在这个阶段我可以想到两个主要选项:将有问题的 class 包装在一个更可测试的适配器 class 中;或者切换到不同级别的测试。 (我更喜欢后者,我将在下面解释。)

第一个选项是这样的:

public interface IDbContextConfiguration {
    bool AutoDetectChangesEnabled { get; set; }
    // ... any other required members here ...
}

public class DbContextConfigurationAdapter : IDbContextConfiguration {
    DbContextConfiguration config;
    public DbContextConfigurationAdapter(DbContextConfiguration config) {
        this.config = config;
    }
    public bool AutoDetectChangedEnabled {
        get { return config.AutoDetectChangedEnabled; }
        set { config = value; }
    }
}

然后更新 IInventoryDatabase 以使用更易测试的 IDbContextConfiguration 类型。我反对这种方法,因为它最终可能需要为一些本应相当简单的事情做大量工作。这种方法对于我们有合理的行为可以在逻辑接口下分组的情况非常有用,但是对于使用 AutoDetectChangedEnabled 属性 这似乎是不必要的工作。

另一种选择是在不同级别对此进行测试。我认为测试当前代码的困难在于我们试图替代 Entity Framework 的细节,而不是我们为划分应用程序的逻辑细节而创建的接口。搜索 "don't mock types you don't own" 以获取有关为什么这可能成为问题的更多信息(我在 here 之前已经写过)。

一个不同级别测试的例子是切换到内存数据库来测试这部分代码。这将告诉您更多有价值的信息:给定测试数据库的已知状态,您正在演示查询 return 预期信息。这与显示我们以我们认为需要的方式调用 Entity Framework 的测试形成对比。

为了将这种方法与模拟相结合(不一定需要!),我们可以创建一个更高级别的接口并替换它来测试我们的应用程序代码,然后实现该接口并使用内存中的测试数据库。然后我们将应用程序分为两个部分,我们可以独立测试:首先,我们的应用程序正确使用数据访问接口中的数据,其次,我们对该接口的实现按预期工作。

所以这会给我们这样的东西:

public interface IAppDatabase {
    // These members just for example. Maybe instead of something general like
    // `GetAllNames()` we have operations specific to app operations such as
    // `UpdateAddress(Guid id, Address newAddress)`, `GetNameFor(SomeParams p)` etc.
    Task<List<Name>> GetAllNames();
    Task<Address> LookupAddress(Guid id);
}

public class AppDatabase : IAppDatabase {
    // ...
    public AppDatabase(IInventoryDatabase db) { ... }

    public Task<List<Name>> GetAllNames() {
        // use `db` and Entity Framework to retrieve data...
    }
    // ...
}

AppDatabase class 我们使用内存数据库进行测试。我们针对替代品 IAppDatabase.

测试的应用程序的其余部分

请注意,我们可以通过使用内存数据库进行所有相关测试来跳过此处的模拟步骤。使用模拟可能比在数据库中设置所有必需的数据更容易,或者可以使测试 运行 更快。也可能不是——我建议考虑这两种选择。

希望这对您有所帮助。