Причина 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
.
Обратите внимание, что здесь мы можем пропустить шаг насмешки, используя базу данных в памяти для всех соответствующих тестов.Использование макетов может быть проще, чем установка всех необходимых данных в базе данных, или может ускорить выполнение тестов.Или, может быть, нет - я предлагаю рассмотреть оба варианта.
Надеюсь, это поможет.