Pregunta

Encontré una clase durante mi trabajo que se ve así:

public class MyObject
{
  public int? A {get; set;}
  public int? B {get; set;}
  public int? C {get; set;}
  public virtual int? GetSomeValue()
  {
    //simplified behavior:
    return A ?? B ?? C;
  }  
}

El problema es que tengo algún código que accede a A, B y C y llama al método GetSomevalue () (ahora, diría que este no es un buen diseño, pero a veces mis manos están atadas ;-)). Quiero crear un simulacro de este objeto, que, al mismo tiempo, tiene A, B y C establecido en algunos valores. Entonces, cuando uso MOQ como tal:

var m = new Mock<MyObject>() { DefaultValue = DefaultValue.Mock };

Me permite configurar un resultado en el método getSomeValue (), pero todas las propiedades están configuradas en NULL (y configurar todas ellas usando Setup () es bastante engorroso, ya que el objeto real es un objeto de datos desagradable y tiene más propiedades que en arriba ejemplo simplificado).

Entonces, por otro lado, usando autofixtura como esta:

var fixture = new Fixture();
var anyMyObject = fixture.CreateAnonymous<MyObject>();

Me deja sin la capacidad de estupar un método getSomevalue ().

¿Hay alguna forma de combinar los dos, tener valores anónimos y la capacidad de configurar los resultados de las llamadas?

Editar

Basado en la respuesta de Nemesv, derivé el siguiente método de utilidad (espero que lo haya hecho bien):

public static Mock<T> AnonymousMock<T>() where T : class
{
  var mock = new Mock<T>();
  fixture.Customize<T>(c => c.FromFactory(() => mock.Object));
  fixture.CreateAnonymous<T>();
  fixture.Customizations.RemoveAt(0);
  return mock;
}
¿Fue útil?

Solución

Este es En realidad, es posible hacer con la autofixtura, pero requiere un poco de ajuste. Los puntos de extensibilidad están ahí, pero admito que en este caso, la solución no es particularmente descubierta.

Se vuelve aún más difícil si desea que funcione con tipos anidados/complejos.

Dado que MyObject clase anterior, así como esta MyParent clase:

public class MyParent
{
    public MyObject Object { get; set; }

    public string Text { get; set; }
}

Todas estas pruebas unitarias pasan:

public class Scenario
{
    [Fact]
    public void CreateMyObject()
    {
        var fixture = new Fixture().Customize(new MockHybridCustomization());

        var actual = fixture.CreateAnonymous<MyObject>();

        Assert.NotNull(actual.A);
        Assert.NotNull(actual.B);
        Assert.NotNull(actual.C);
    }

    [Fact]
    public void MyObjectIsMock()
    {
        var fixture = new Fixture().Customize(new MockHybridCustomization());

        var actual = fixture.CreateAnonymous<MyObject>();

        Assert.NotNull(Mock.Get(actual));
    }

    [Fact]
    public void CreateMyParent()
    {
        var fixture = new Fixture().Customize(new MockHybridCustomization());

        var actual = fixture.CreateAnonymous<MyParent>();

        Assert.NotNull(actual.Object);
        Assert.NotNull(actual.Text);
        Assert.NotNull(Mock.Get(actual.Object));
    }

    [Fact]
    public void MyParentIsMock()
    {
        var fixture = new Fixture().Customize(new MockHybridCustomization());

        var actual = fixture.CreateAnonymous<MyParent>();

        Assert.NotNull(Mock.Get(actual));
    }
}

¿Qué hay en Mockhybridcustomization? Este:

public class MockHybridCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customizations.Add(
            new MockPostprocessor(
                new MethodInvoker(
                    new MockConstructorQuery())));
        fixture.Customizations.Add(
            new Postprocessor(
                new MockRelay(t =>
                    t == typeof(MyObject) || t == typeof(MyParent)),
                new AutoExceptMoqPropertiesCommand().Execute,
                new AnyTypeSpecification()));
    }
}

los MockPostprocessor, MockConstructorQuery y MockRelay Las clases se definen en el Extensión automotriz a la autofixtura, por lo que deberá agregar una referencia a esta biblioteca. Sin embargo, tenga en cuenta que no es necesario agregar el AutoMoqCustomization.

los AutoExceptMoqPropertiesCommand La clase también está construida a medida para la ocasión:

public class AutoExceptMoqPropertiesCommand : AutoPropertiesCommand<object>
{
    public AutoExceptMoqPropertiesCommand()
        : base(new NoInterceptorsSpecification())
    {
    }

    protected override Type GetSpecimenType(object specimen)
    {
        return specimen.GetType();
    }

    private class NoInterceptorsSpecification : IRequestSpecification
    {
        public bool IsSatisfiedBy(object request)
        {
            var fi = request as FieldInfo;
            if (fi != null)
            {
                if (fi.Name == "__interceptors")
                    return false;
            }

            return true;
        }
    }
}

Esta solución proporciona una solución general a la pregunta. Sin embargo, no se ha probado ampliamente, por lo que me encantaría recibir comentarios al respecto.

Otros consejos

Probablemente hay un mejor por qué, pero esto funciona:

var fixture = new Fixture();
var moq = new Mock<MyObject>() { DefaultValue = DefaultValue.Mock };
moq.Setup(m => m.GetSomeValue()).Returns(3);

fixture.Customize<MyObject>(c => c.FromFactory(() => moq.Object));

var anyMyObject = fixture.CreateAnonymous<MyObject>();

Assert.AreEqual(3, anyMyObject.GetSomeValue());
Assert.IsNotNull(anyMyObject.A);
//...

Inicialmente intenté usar fixture.Register(() => moq.Object); en vez de fixture.Customize pero registra la función creadora con OmitAutoProperties() Entonces no funcionaría para tu caso.

A partir de 3.20.0, puede usar AutoConfiguredMoqCustomization. Esto configurará automáticamente todos los simulacros para que los valores de devolución de sus miembros se generen por autofixture.

var fixture = new Fixture().Customize(new AutoConfiguredMoqCustomization());

var mock = fixture.Create<Mock<MyObject>>();

Assert.NotNull(mock.Object.A);
Assert.NotNull(mock.Object.B);
Assert.NotNull(mock.Object.C);
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top