How to test DB.Configuration.AutoDetectChangesEnabled = false

北城以北 提交于 2019-12-11 05:14:46

问题


I'm trying to write some tests for a class using NSubstitute.

Class constructor is:

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

Here is my UnitTests 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);      
    }

Whenever I try to test some method, the test fails and there is a NullReferenceException and it goes in StackTrace to the SetUp method.

When I commented out the this.DB.Configuration.AutoDetectChangesEnabled = false; in ClassToTest constructor the tests work fine.

Edit:

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

回答1:


The reason for the NullReferenceException is that NSubstitute cannot automatically substitute for DbContextConfiguration (it can only do so for purely virtual classes).

Normally we could work around this by manually configuration this property, something like Db.Configuration.Returns(myConfiguration), but in this case DbContextConfiguration does not seem to have a public constructor so we are unable to create an instance for myConfiguration.

At this stage I can think of two main options: wrap the problematic class in a more testable adapter class; or switch to testing this at a different level. (My preference is the latter which I'll explain below.)

The first option involves something like this:

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

Then updating IInventoryDatabase to using the more testable IDbContextConfiguration type. My opposition to this approach is that it can end up requiring a lot of work for something that should be fairly simple. This approach can be very useful for cases where we have behaviours that make sense to be grouped under a logical interface, but for working with an AutoDetectChangedEnabled property this seems unnecessary work.

The other option is to test this at a different level. I think the friction in testing the current code is that we are trying to substitute for details of Entity Framework, rather than interfaces we've created for partitioning the logical details of our app. Search for "don't mock types you don't own" for more information on why this can be a problem (I've written about it before here).

One example of testing at a different level is to switch to an in-memory database for testing this part of the code. This will tell you much more valuable information: given a known state of the test database, you are demonstrating the queries return the expected information. This is in contrast to a test showing we are calling Entity Framework in the way we think is required.

To combine this approach with mocking (not necessarily required!), we can create a higher level interface and substitute for that for testing our application code, then make an implementation of that interface and test that using the in-memory database. We have then divided the application into two parts that we can test independently: first that our app uses data from the data access interface correctly, and secondly that our implementation of that interface works as expected.

So that would give us something like this:

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...
    }
    // ...
}

The AppDatabase class we test with an in-memory database. The rest of the app we test with respect to a substitute IAppDatabase.

Note that we can skip the mocking step here by using the in-memory database for all relevant tests. Using mocking may be easier than setting up all the required data in the database, or may make tests run faster. Or maybe not -- I suggest considering both options.

Hope this helps.



来源:https://stackoverflow.com/questions/53415138/how-to-test-db-configuration-autodetectchangesenabled-false

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!