Издевательство над зависимостью с помощью автофиксации
-
21-12-2019 - |
Вопрос
Недавно я начал использовать AutoFixture+AutoMoq и пытаюсь создать экземпляр Func<IDbConnection>
(т.е. фабрика соединений).
var fixture = new Fixture().Customize(new AutoMoqCustomization());
var connectionFactory = fixture.Create<Func<IDbConnection>>();
Кажется, это работает довольно хорошо:
- Моя тестируемая система может вызвать делегата, и он получит макет
IDbConnection
- По которому я затем могу позвонить
CreateCommand
, что даст мне возможность посмеяться надIDbCommand
- По которому я затем могу позвонить
ExecuteReader
, что даст мне возможность посмеяться надIDataReader
Теперь я хочу выполнить дополнительные настройки на макете IDataReader
, например , заставить его вернуться true
когда Read()
называется.
Из того, что я прочитал, я должен использовать Freeze
для этого:
var dataReaderMock = fixture.Freeze<Mock<IDataReader>>();
dataReaderMock.Setup(dr => dr.Read())
.Returns(true);
Однако, похоже, это не соответствует моим ожиданиям.Когда я звоню IDbCommand.ExecuteReader
, я получу другой считыватель, отличный от того, который я только что заморозил / настроил.
Вот пример:
var fixture = new Fixture().Customize(new AutoMoqCustomization());
var dataReaderMock = fixture.Freeze<Mock<IDataReader>>();
dataReaderMock.Setup(dr => dr.Read())
.Returns(true);
//true - Create<IDataReader> retrieves the data reader I just mocked
Assert.AreSame(dataReaderMock.Object, fixture.Create<IDataReader>());
//false - IDbCommand returns a different instance of IDataReader
Assert.AreSame(dataReaderMock.Object, fixture.Create<IDbCommand>().ExecuteReader());
Что я делаю не так?Как мне получить другие приспособления, такие как IDbCommand
, чтобы использовать издевательский экземпляр IDataReader
?
Решение
Начиная с версии 3.20.0, вы можете использовать AutoConfiguredMoqCustomization
.Это автоматически настроит все макеты таким образом, чтобы возвращаемые значения их элементов генерировались с помощью автофиксации.
Напр., IDbConnetion.CreateCommand
будет автоматически сконфигурирован для возврата IDbCommand
от приспособления, и IDbCommand.ExecuteReader
будет автоматически сконфигурирован для возврата IDataReader
от светильника.
Все эти тесты должны пройти сейчас:
var fixture = new Fixture().Customize(new AutoConfiguredMoqCustomization());
var dataReaderMock = fixture.Freeze<Mock<IDataReader>>();
dataReaderMock.Setup(dr => dr.Read())
.Returns(true);
//all pass
Assert.Same(dataReaderMock.Object, fixture.Create<IDataReader>());
Assert.Same(dataReaderMock.Object, fixture.Create<IDbCommand>().ExecuteReader());
Assert.Same(dataReaderMock.Object, fixture.Create<IDbConnection>().CreateCommand().ExecuteReader());
Assert.Same(dataReaderMock.Object, fixture.Create<Func<IDbConnection>>()().CreateCommand().ExecuteReader());
Другие советы
Ты должен Freeze
то Mock<IDbCommand>
также – и настройте макетный объект (как заглушку) для возврата существующего dataReaderMock.Object
пример.
Если вы добавите следующее к этапу подготовки вашего теста, тест будет пройден:
var dbCommandStub =
fixture
.Freeze<Mock<IDbCommand>>()
.Setup(x => x.ExecuteReader())
.Returns(dataReaderMock.Object);
Хотя решение от Nikos работает, я бы не стал, я не рекомендую издеваться ado.net.
На мой взгляд, ваши тесты, вероятно, будут трудны для понимания, сопровождения и не дадут вам той уверенности, которую должны дать вам ваши тесты.
Я бы подумал о том, чтобы протестировать ваш уровень данных, пройдя весь путь до базы данных, даже если это медленнее.
Я бы порекомендовал прочитать эту статью о лучших практиках издевательства:http://codebetter.com/jeremymiller/2006/01/10/best-and-worst-practices-for-mock-objects/
Не насмехайтесь над другими:http://aspiringcraftsman.com/2012/04/01/tdd-best-practices-dont-mock-others/
Я не знаю вашей точной ситуации, но в любом случае я хотел поделиться этим.