Creating a hybrid layout and anonymous object using, for example, Moq and AutoFixture?

During my work, I came across a class that looks like this:

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; } } 

The problem is that I have code that accesses A, B and C and calls the GetSomeValue () method (now I would say that this is not a very good design, but sometimes my hands are tied ;-)). I want to create a layout for this object, which at the same time has the values ​​A, B and C for some values. So, when I use moq as such:

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

allows me to adjust the result using the GetSomeValue () method, but all the properties are null (and setting all of them using the installer () is rather cumbersome, since the real object is an unpleasant data object and has more properties than in the simplified example).

On the other hand, using AutoFixture as follows:

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

It leaves me unable to call the GetSomeValue () method call.

Is there a way to combine the two, have anonymous values ​​and the ability to customize the results of the call?

Edit

Based on nemesv's answer, I got the following utility method (hope everything is correct):

 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; } 
+6
source share
3 answers

In fact, this can be done with AutoFixture, but this requires a little setup. There are all extensibility points, but I admit that in this case the solution is not particularly accessible for detection.

This gets even more complicated if you want it to work with nested / complex types.

Given the MyObject class above, as well as this MyParent class:

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

All of these unit tests pass:

 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)); } } 

What's in MockHybridCustomization? It:

 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())); } } 

The MockPostprocessor , MockConstructorQuery and MockRelay defined in the AutoMoq extension in AutoFixture, so you will need to add a link to this library. However, note that AutoMoqCustomization not required to be AutoMoqCustomization .

The AutoExceptMoqPropertiesCommand class AutoExceptMoqPropertiesCommand also configurable for this occasion:

 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; } } } 

This solution provides a general solution to the issue. However, it has not been thoroughly tested, so I would like to receive feedback from it.

+5
source

There is probably something better, but it works:

 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); //... 

Initially, I tried to use fixture.Register(() => moq.Object); instead of fixture.Customize , but it registers the creator function with OmitAutoProperties() , so it won’t work for you.

+4
source

Starting with 3.20.0, you can use AutoConfiguredMoqCustomization . This will automatically configure all mocks so that the return values ​​of their members are generated using 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); 
+2
source

Source: https://habr.com/ru/post/908774/


All Articles