Search code examples
unit-testingmoqxunitautofixtureautomoq

How to automoq concrete class dependencies through Automoq?


In our class constructor, we had multiple classes dependencies. as per Automoq documentation, we should have dependency of interface or abstraction.

Code Setup

  • System Under Test Class have dependency of ManageLocationRepository as class dependency, demonstrated in below code snippet.
public class CityEventListener : IEvent<LocationChangeEventData>
    {
        private readonly ILocationAdapterCaller _locationAdapterCaller;
        private readonly ManageLocationRepository _managerLocationRepository;

        public CityEventListener(ILocationAdapterCaller locationAdapterCaller, ManageLocationRepository managerLocationRepository)
        {
            _locationAdapterCaller = locationAdapterCaller;
            _managerLocationRepository = managerLocationRepository;
        }

        public async Task<bool> ProcessEvent(LocationChangeEventData eventData)
        {
        }
    }

Test Case for SUT class -

[Theory(DisplayName = "Valid value test")]
        [ClassAutoMoqData(typeof(ValidValueTests))]
        public async Task ProcessEvent_WithCreateOrUpdateOperation_CallsUpsertCityAndReturnsResult(ExpectedValueTestData<Parameters, bool> data,
        [Frozen] Mock<ILocationAdapterCaller> locationAdapterCallerMock, [Frozen] Mock<ManageLocationRepository> managerLocationRepositoryMock,
        CityEventListener sut)
        {
            // fakes
            var cityDetail = _fixture.Build<CityDetail>()
                                    .With(x => x.Id, data.Params.locationChangeEventData.Id).Create();

            // Arrange
            locationAdapterCallerMock.Setup(mock => mock.GetCityDetail(data.Params.locationChangeEventData.Id))
                .ReturnsAsync(cityDetail).Verifiable();

            managerLocationRepositoryMock
                .Setup(mock => mock.UpsertCity(cityDetail))
                .ReturnsAsync(data.ExpectedValue).Verifiable();

            var result = await sut.ProcessEvent(data.Params.locationChangeEventData);

            // Assert
            using (new AssertionScope())
            {
                Assert.IsType<bool>(result);
                Assert.Equal(data.ExpectedValue, result);
                locationAdapterCallerMock.Verify();
                managerLocationRepositoryMock.Verify();
            }
        }

Support for Class AutoMoq dependencies

public class ClassAutoMoqDataAttribute : CompositeDataAttribute
    {
        public ClassAutoMoqDataAttribute(Type values)
            : base(new ClassDataAttribute(values), new AutoMoqDataAttribute())
        {
        }
    }

public class AutoMoqDataAttribute : AutoDataAttribute
{
    public AutoMoqDataAttribute() : base(() =>
    {
        var fixture = new Fixture().Customize(new CompositeCustomization(
            new AutoMoqCustomization() { ConfigureMembers = true, GenerateDelegates = true },
            new SupportMutableValueTypesCustomization()));

        fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList().ForEach(b => fixture.Behaviors.Remove(b));
        fixture.Behaviors.Add(new OmitOnRecursionBehavior());

        return fixture;
    })
    {
    }
}

Problem: Although the configuration in the above illustration may automoq class dependencies, but I am unable to obtain a frozen instance of the same class.

How can I use the class automoq attribute to better achieve automoq frozen dependencies?


Solution

  • I solved my problem by taking two actions -

    1. mark concrete class method virtual
    2. create custom class automoq attribute to freeze dependencies
    public class CityEventListenerClassAutoMoqAttribute : CompositeDataAttribute
       {
           public CityEventListenerClassAutoMoqAttribute(Type values)
               : base(new ClassDataAttribute(values), new CityEventListenerAutoMoqAttribute())
           {
           }
       }
    
       public class CityEventListenerAutoMoqAttribute : AutoDataAttribute
       {
           public CityEventListenerAutoMoqAttribute()
               : base(() =>
       {
           var fixture = new Fixture().Customize(new CompositeCustomization(
               new AutoMoqCustomization() { ConfigureMembers = true, GenerateDelegates = true },
               new SupportMutableValueTypesCustomization()));
           var managerLocationRepositoryMock =
                   fixture.Freeze<Mock<ManageLocationRepository>>();
           fixture.Inject(managerLocationRepositoryMock.Object);
           return fixture;
       })
           {
           }
       }
    

    Now my test case looks like this -

    [Theory(DisplayName = "Valid value test")]
            [CityEventListenerClassAutoMoq(typeof(ValidValueTests))]
            public async Task ProcessEvent_WithCreateOrUpdateOperation_CallsUpsertCityAndReturnsResult(ExpectedValueTestData<Parameters, bool> data,
            [Frozen] Mock<ILocationAdapterCaller> locationAdapterCallerMock, [Frozen] Mock<ManageLocationRepository> managerLocationRepositoryMock,
            CityEventListener sut)
            {
                // fakes
                var cityDetail = _fixture.Build<CityDetail>()
                                        .With(x => x.Id, data.Params.locationChangeEventData.Id).Create();
    
                // Arrange
                locationAdapterCallerMock.Setup(mock => mock.GetCityDetail(data.Params.locationChangeEventData.Id))
                    .ReturnsAsync(cityDetail).Verifiable();
    
                managerLocationRepositoryMock
                    .Setup(mock => mock.UpsertCity(cityDetail))
                    .ReturnsAsync(data.ExpectedValue).Verifiable();
    
                var result = await sut.ProcessEvent(data.Params.locationChangeEventData);
    
                // Assert
                using (new AssertionScope())
                {
                    Assert.IsType<bool>(result);
                    Assert.Equal(data.ExpectedValue, result);
                    locationAdapterCallerMock.Verify();
                    managerLocationRepositoryMock.Verify();
                }
            }
    

    Do let me know if I can improve anything in my approach.