Короткий вывод, когда решение найдено:
AutoFixture просто возвращает замороженный макет; у меня сут, который также был сгенерирован AutoFixture, просто имел общедоступное свойство с локальным значением по умолчанию, которое было важно для теста, и что AutoFixture было установлено на новое значение. Из ответа Марка можно еще многому научиться.
Исходный вопрос:
Вчера я начал опробовать AutoFixture для своих тестов xUnit.net, в которых повсюду присутствует Moq. Я надеялся заменить некоторые элементы Moq или упростить их чтение, и меня особенно интересует использование AutoFixture в емкости SUT Factory.
Я вооружился несколькими сообщениями в блоге Марка Симанна об AutoMocking и попытался продолжить работу, но далеко не продвинулся.
Вот как выглядел мой тест без AutoFixture:
[Fact]
public void GetXml_ReturnsCorrectXElement()
{
// Arrange
string xmlString = @"
<mappings>
<mapping source='gcnm_loan_amount_min' target='gcnm_loan_amount_min_usd' />
<mapping source='gcnm_loan_amount_max' target='gcnm_loan_amount_max_usd' />
</mappings>";
string settingKey = "gcCreditApplicationUsdFieldMappings";
Mock<ISettings> settingsMock = new Mock<ISettings>();
settingsMock.Setup(s => s.Get(settingKey)).Returns(xmlString);
ISettings settings = settingsMock.Object;
ITracingService tracing = new Mock<ITracingService>().Object;
XElement expectedXml = XElement.Parse(xmlString);
IMappingXml sut = new SettingMappingXml(settings, tracing);
// Act
XElement actualXml = sut.GetXml();
// Assert
Assert.True(XNode.DeepEquals(expectedXml, actualXml));
}
История здесь достаточно проста - убедитесь, что SettingMappingXml
запрашивает ISettings
зависимость с правильным ключом (который жестко закодирован / введено свойство) и возвращает результат в виде XElement
. ITracingService
актуален только в случае ошибки.
Я пытался избавиться от необходимости явно создавать объект ITracingService
, а затем вручную вводить зависимости (не потому, что этот тест слишком сложен, а потому, что он достаточно прост, чтобы попробовать и понять их).
Войдите в AutoFixture - первая попытка:
[Fact]
public void GetXml_ReturnsCorrectXElement()
{
// Arrange
IFixture fixture = new Fixture();
fixture.Customize(new AutoMoqCustomization());
string xmlString = @"
<mappings>
<mapping source='gcnm_loan_amount_min' target='gcnm_loan_amount_min_usd' />
<mapping source='gcnm_loan_amount_max' target='gcnm_loan_amount_max_usd' />
</mappings>";
string settingKey = "gcCreditApplicationUsdFieldMappings";
Mock<ISettings> settingsMock = new Mock<ISettings>();
settingsMock.Setup(s => s.Get(settingKey)).Returns(xmlString);
ISettings settings = settingsMock.Object;
fixture.Inject(settings);
XElement expectedXml = XElement.Parse(xmlString);
IMappingXml sut = fixture.CreateAnonymous<SettingMappingXml>();
// Act
XElement actualXml = sut.GetXml();
// Assert
Assert.True(XNode.DeepEquals(expectedXml, actualXml));
}
Я ожидаю, что CreateAnonymous<SettingMappingXml>()
при обнаружении параметра конструктора ISettings
заметит, что конкретный экземпляр был зарегистрирован для этого интерфейса, и внедрит его - однако он этого не делает, а вместо этого создает новую анонимную реализацию.
Это особенно сбивает с толку, поскольку fixture.CreateAnonymous<ISettings>()
действительно возвращает мой экземпляр -
IMappingXml sut = new SettingMappingXml(fixture.CreateAnonymous<ISettings>(), fixture.CreateAnonymous<ITracingService>());
делает тест совершенно зеленым, и эта строка - это именно то, что я ожидал от AutoFixture при создании экземпляра SettingMappingXml
.
Затем есть концепция замораживания компонента, поэтому я просто заморозил фиктивный объект в приспособлении вместо получения фиктивного объекта:
fixture.Freeze<Mock<ISettings>>(f => f.Do(m => m.Setup(s => s.Get(settingKey)).Returns(xmlString)));
Конечно, это прекрасно работает - пока я вызываю конструктор SettingMappingXml
явно и не полагаюсь на CreateAnonymous()
.
Проще говоря, я не понимаю, почему это работает так, как кажется, поскольку это противоречит любой логике, которую я могу придумать. Обычно я подозреваю ошибку в библиотеке, но это что-то настолько простое, что я уверен, что другие столкнулись бы с этим, и это уже давно было бы найдено и исправлено. Более того, зная усердный подход Марка к тестированию и DI, это не может быть непреднамеренным.
Это, в свою очередь, означает, что мне не хватает чего-то довольно элементарного. Как я могу создать SUT с помощью AutoFixture с предварительно настроенным имитируемым объектом в качестве зависимости? Единственное, в чем я сейчас уверен, это то, что мне нужен AutoMoqCustomization
, поэтому мне не нужно ничего настраивать для ITracingService
.
Пакеты AutoFixture / AutoMoq - 2.14.1, Moq - 3.1.416.3, все из NuGet. Версия .NET - 4.5 (установлена с VS2012), поведение такое же в VS2012 и 2010.
Во время написания этого сообщения я обнаружил, что у некоторых людей были проблемы с Moq 4.0 и перенаправлением привязки сборки, поэтому я тщательно очистил свое решение от любых экземпляров Moq 4 и установил Moq 3.1, установив AutoFixture.AutoMoq в «чистые» проекты. Однако поведение моего теста не изменилось.
Спасибо за любые указатели и объяснения.
Обновление. Вот код конструктора, который запрашивал Марк:
public SettingMappingXml(ISettings settingSource, ITracingService tracing)
{
this._settingSource = settingSource;
this._tracing = tracing;
this.SettingKey = "gcCreditApplicationUsdFieldMappings";
}
А для полноты метод GetXml()
выглядит так:
public XElement GetXml()
{
int errorCode = 10600;
try
{
string mappingSetting = this._settingSource.Get(this.SettingKey);
errorCode++;
XElement mappingXml = XElement.Parse(mappingSetting);
errorCode++;
return mappingXml;
}
catch (Exception e)
{
this._tracing.Trace(errorCode, e.Message);
throw;
}
}
SettingKey
- это просто автоматическое свойство.