Creazione di un ibrido di un oggetto finto e anonimo usando EG MOQ e AutoFixture?
-
27-10-2019 - |
Domanda
Ho incontrato una lezione durante il mio lavoro che assomiglia a questo:
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;
}
}
Il problema è che ho del codice che accede a A, B e C e chiama il metodo getomeValue () (ora, direi che questo non è un buon design, ma a volte le mie mani sono legate ;-)). Voglio creare un dombo di questo oggetto, che, allo stesso tempo, ha A, B e C impostato su alcuni valori. Quindi, quando uso MOQ come tale:
var m = new Mock<MyObject>() { DefaultValue = DefaultValue.Mock };
Permettimi di configurare un risultato sul metodo getomeValue (), ma tutte le proprietà sono impostate su null (e impostarle tutte utilizzando setup () è piuttosto ingombra Esempio semplificato).
Quindi, d'altra parte, usando AutoFixture in questo modo:
var fixture = new Fixture();
var anyMyObject = fixture.CreateAnonymous<MyObject>();
Mi lascia senza la capacità di stupire una chiamata al metodo getomeValue ().
Esiste un modo per combinare i due, per avere valori anonimi e la capacità di configurare i risultati delle chiamate?
Modificare
Sulla base della risposta di NEMESV, ho derivato il seguente metodo di utilità (spero di averlo capito bene):
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;
}
Soluzione
Questo è In realtà è possibile fare con l'autofixture, ma richiede un po 'di modifica. I punti di estensibilità sono tutti lì, ma ammetto che in questo caso la soluzione non è particolarmente rilevabile.
Diventa ancora più difficile se vuoi che funzioni con tipi nidificati/complessi.
dato che MyObject
classe sopra, così come questa MyParent
classe:
public class MyParent
{
public MyObject Object { get; set; }
public string Text { get; set; }
}
Questi test unitari passano tutti:
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));
}
}
Cosa c'è nella fintacustomizzazione? Questo:
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()));
}
}
Il MockPostprocessor
, MockConstructorQuery
e MockRelay
Le classi sono definite in Estensione automobilistica Per automaticamente, quindi dovrai aggiungere un riferimento a questa libreria. Tuttavia, si noti che non è necessario aggiungere il AutoMoqCustomization
.
Il AutoExceptMoqPropertiesCommand
La classe è anche costruita su misura per l'occasione:
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;
}
}
}
Questa soluzione fornisce una soluzione generale alla domanda. Tuttavia, non è stato ampiamente testato, quindi mi piacerebbe ricevere feedback.
Altri suggerimenti
Probabilmente c'è un perché, ma funziona:
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);
//...
Inizialmente ho provato a usare fixture.Register(() => moq.Object);
invece di fixture.Customize
ma registra la funzione creatore con OmitAutoProperties()
Quindi non funzionerebbe per te.
As of 3.20.0, you can use AutoConfiguredMoqCustomization
. This will automatically configure all mocks so that their members' return values are generated by 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);