AutoFixture / AutoMoq ignora instância injetada / falsa congelada
O breve resumo agora que a solução foi encontrada:
AutoFixture retorna congelado o mock muito bem; meu sut que também foi gerado pelo AutoFixture tinha uma propriedade pública com um padrão local que era importante para o teste e que o AutoFixture definia como um novo valor. Há muito o que aprender além da resposta de Mark.
Pergunta original:
Comecei a testar o AutoFixture ontem para os meus testes do xUnit.net que têm o Moq por cima. Eu esperava substituir algumas das coisas do Moq ou facilitar a leitura, e estou especialmente interessado em usar o AutoFixture na capacidade da Fábrica da SUT.
Armei-me com alguns dos posts de Mark Seemann no AutoMocking e tentei trabalhar a partir daí, mas não cheguei muito longe.
Isto é o que meu teste parecia sem 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));
}
A história aqui é simples o suficiente - certifique-se de queSettingMappingXml
consulta oISettings
dependência com a chave correta (que é codificada / propriedade injetada) e retorna o resultado comoXElement
. oITracingService
é relevante apenas se houver um erro.
O que eu estava tentando fazer é se livrar da necessidade de criar explicitamente oITracingService
objeto e, em seguida, injetar manualmente as dependências (não porque este teste é muito complexo, mas porque é simples o suficiente para experimentar e entender).
Digite AutoFixture - primeira tentativa:
[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));
}
eu esperariaCreateAnonymous<SettingMappingXml>()
, após a detecção doISettings
constructor, para notar que uma instância concreta foi registrada para aquela interface e injetar isso - no entanto, ela não faz isso, mas cria uma nova implementação anônima.
Isso é especialmente confusofixture.CreateAnonymous<ISettings>()
realmente devolve minha instância -
IMappingXml sut = new SettingMappingXml(fixture.CreateAnonymous<ISettings>(), fixture.CreateAnonymous<ITracingService>());
faz o teste perfeitamente verde, e esta linha é exatamente o que eu esperava que o AutoFixture fizesse internamente ao instanciarSettingMappingXml
.
Depois, há o conceito de congelamento de um componente, então fui em frente apenas congelando o mock no dispositivo, em vez de obter o objeto zombado:
fixture.Freeze<Mock<ISettings>>(f => f.Do(m => m.Setup(s => s.Get(settingKey)).Returns(xmlString)));
Com certeza isso funciona perfeitamente bem - desde que eu chame oSettingMappingXml
construtor explicitamente e não confie emCreateAnonymous()
.
Simplificando, não entendo por que funciona do jeito que aparentemente faz, pois isso vai contra qualquer lógica que eu possa conjurar. Normalmente, eu suspeitaria de um bug na biblioteca, mas isso é algo tão básico que tenho certeza de que outros teriam encontrado isso e ele teria sido encontrado e consertado. Além do mais, conhecendo a abordagem assídua de Mark para testar e DI, isso não pode ser involuntário.
Isso, por sua vez, significa que devo estar perdendo algo bastante elementar. Como posso ter meu SUT criado por AutoFixture com um objeto falsificado pré-configurado como uma dependência? A única coisa de que tenho certeza agora é que preciso doAutoMoqCustomization
então eu não tenho que configurar nada para oITracingService
.
Os pacotes AutoFixture / AutoMoq são 2.14.1, o Moq é 3.1.416.3, todos do NuGet. Versão .NET é 4.5 (instalado com VS2012), o comportamento é o mesmo em VS2012 e 2010.
Enquanto escrevia este post, descobri que algumas pessoas estavam tendo problemas com o Moq 4.0 e redirecionamentos de ligação de assembly, então eu meticulosamente removi minha solução de quaisquer instâncias do Moq 4 e instalei o Moq 3.1 instalando o AutoFixture.AutoMoq em projetos "limpos". No entanto, o comportamento do meu teste permanece inalterado.
Obrigado por quaisquer indicações e explicações.
Atualizar: Aqui está o código do construtor que Mark pediu:
public SettingMappingXml(ISettings settingSource, ITracingService tracing)
{
this._settingSource = settingSource;
this._tracing = tracing;
this.SettingKey = "gcCreditApplicationUsdFieldMappings";
}
E para completar, oGetXml()
método é assim:
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
é apenas uma propriedade automática.