Как проверить DB.Configuration.AutoDetectChangesEnabled = false - PullRequest
0 голосов
/ 21 ноября 2018

Я пытаюсь написать несколько тестов для класса, используя 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();
   }

1 Ответ

0 голосов
/ 26 ноября 2018

Причина NullReferenceException заключается в том, что NSubstitute не может автоматически заменить DbContextConfiguration (это может быть сделано только для чисто виртуальных классов ).

Обычно мы могли бы обойти это, вручную сконфигурировав это свойство, что-то вроде Db.Configuration.Returns(myConfiguration), но в этом случае DbContextConfiguration, похоже, не имеет публичного конструктора, поэтому мы не можем создать экземпляр для 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тип.Я возражаю против этого подхода в том, что он может потребовать много работы для чего-то, что должно быть довольно простым.Этот подход может быть очень полезен для случаев, когда у нас есть поведения, которые имеют смысл группировать по логическому интерфейсу, но для работы со свойством AutoDetectChangedEnabled это кажется ненужной работой.

Другой вариант - проверить этона другом уровне.Я думаю, что трение в тестировании текущего кода состоит в том, что мы пытаемся заменить детали Entity Framework, а не интерфейсы, которые мы создали для разделения логических деталей нашего приложения.Ищите «не издевайтесь над типами, которые вам не принадлежат» для получения дополнительной информации о том, почему это может быть проблемой (я писал об этом раньше здесь ).

Один примертестирование на другом уровне - переключение на базу данных в памяти для тестирования этой части кода.Это скажет вам гораздо более ценную информацию: учитывая известное состояние тестовой базы данных, вы демонстрируете, что запросы возвращают ожидаемую информацию.Это в отличие от теста, показывающего, что мы вызываем 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, который мы тестируем с базой данных в памяти.Остальную часть приложения мы тестируем на предмет замены IAppDatabase.

Обратите внимание, что здесь мы можем пропустить шаг насмешки, используя базу данных в памяти для всех соответствующих тестов.Использование макетов может быть проще, чем установка всех необходимых данных в базе данных, или может ускорить выполнение тестов.Или, может быть, нет - я предлагаю рассмотреть оба варианта.

Надеюсь, это поможет.

...