我正在尝试使用NSubstitute为类编写一些测试。
类的构造函数是:
public class ClassToTest : IClassToTest
{
private IDataBase DB;
public ClassToTest(IDatabase DB)
{
this.DB = DB;
this.DB.Configuration.AutoDetectChangesEnabled = false;
}
这是我的UnitTests类:
[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();
}
答案 0 :(得分:0)
使用NullReferenceException
的原因是NSubstitute无法自动替换DbContextConfiguration
(只能替换purely virtual classes)。
通常,我们可以通过手动配置此属性来解决此问题,例如Db.Configuration.Returns(myConfiguration)
,但是在这种情况下,DbContextConfiguration
似乎没有公共构造函数,因此我们无法为{ {1}}。
在这个阶段,我可以想到两个主要的选择:将有问题的类包装在可测试的适配器类中;或切换到其他级别进行测试。 (我更喜欢后者,我将在下面解释。)
第一个选项涉及以下内容:
myConfiguration
然后将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
属性来说,这似乎是不必要的工作。
另一个选择是在不同级别进行测试。我认为测试当前代码的摩擦在于,我们试图替代Entity Framework的细节,而不是我们为划分应用程序逻辑细节而创建的接口。搜索“不要嘲笑您不拥有的类型”,以获取更多有关这可能导致问题的原因的信息(我在here之前就已经对此进行过介绍)。
在不同级别进行测试的一个示例是切换到内存数据库以测试这部分代码。这将告诉您更多有价值的信息:给定测试数据库的已知状态,您正在演示查询返回的预期信息。这与一项测试相反,该测试表明我们以我们认为必需的方式调用实体框架。
要将这种方法与模拟结合起来(不一定需要!),我们可以创建一个更高级别的接口并替代它来测试我们的应用程序代码,然后对该接口进行实现并使用内存数据库进行测试。然后,我们将应用程序分为两个部分,我们可以对其进行独立测试:首先,我们的应用程序正确使用了数据访问接口中的数据,其次,我们对该接口的实现按预期工作。
这样可以给我们这样的东西:
AutoDetectChangedEnabled
我们使用内存数据库测试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
测试的其余应用程序。
请注意,我们可以通过使用内存数据库进行所有相关测试来跳过模拟步骤。使用模拟可能比在数据库中设置所有必需的数据容易,或者可能使测试运行更快。也许不是-我建议同时考虑这两种选择。
希望这会有所帮助。