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.

questionAnswers(2)

yourAnswerToTheQuestion