Я пытаюсь написать несколько тестов для класса с помощью 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();
}
@ Дэвид Чепак Это типа: System.Data.Entity.Infrastructure.DbContextConfiguration IDataBase.Configuration {get; } Не могли бы вы показать мне, как это будет выглядеть?
Из MSDN я не вижу общедоступный конструктор для этого типа? В этом случае я не думаю, что вы сможете заглушить свойство Data. Вы можете поделиться определением IDatabase?
@David Tchepak да, похоже, проблема в отсутствии конструктора. Я отредактировал код, чтобы показать вам IDatabase





Причина появления 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.
Обратите внимание, что здесь мы можем пропустить этап имитации, используя базу данных в памяти для всех соответствующих тестов. Использование имитации может быть проще, чем настройка всех необходимых данных в базе данных, или может ускорить выполнение тестов. А может и нет - предлагаю рассмотреть оба варианта.
Надеюсь это поможет.
Что это за тип
Db.Configuration? Если это не интерфейс или чисто виртуальный класс, вам может потребоваться вручную заглушить это свойство:Db.Configuration.Returns(myConfiguration).