2

I have the following setup:

A calendar controller that calls a calendar service that calls a calendar client wrapper that calls the client. Controller -> Service -> ClientWrapper -> Client.

I am making an integration test that mocks the lowest tier (the client) and calls the controller to see if the client was called correctly.

My CalendarControllerBuilder:

internal class CalendarControllerBuilder
{
    public CalendarControllerBuilder()
    {
        CalendarClientMock = new Mock<ICalendarServiceClient>(MockBehavior.Strict);
    }

    public Mock<ICalendarServiceClient> CalendarClientMock { get; set; }

    public CalendarControllerBuilder With(Mock<ICalendarServiceClient> calendarClientWrapperMock)
    {
        CalendarClientMock = calendarClientWrapperMock;
        return this;
    }

    public CalendarController Create()
    {
        var calendarClientWrapperMock = new CalendarClientWrapper(CalendarClientMock.Object);
        
        var calenderService = new CalendarService(calendarClientWrapperMock);
        return new CalendarController(calenderService);
    }
}

Test setup with customization registration:

internal class CalenderControllerCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Register<CalendarController>(() =>
        {
            // ----- ICalendarServiceClient mock setups -----
            var calendarServiceClientMock = new Mock<ICalendarServiceClient>(MockBehavior.Strict);

            calendarServiceClientMock.Setup(m => m.GetEvents(It.IsAny<DateTime>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>()))
                                     .ReturnsAsync(fixture.Create<EventList>()).Verifiable();
            
            return new CalendarControllerBuilder()
                   .With(calendarServiceClientMock)
                   .Create();
        });
    }
}

My automoq data attribute (using AutoFixture.Xunit2):

public class Attributes
{
    public class AutoMoqDataAttribute<T> : AutoDataAttribute where T : ICustomization, new()
    {
        public AutoMoqDataAttribute()
            : base(() => new Fixture()
                       .Customize(
                                  new CompositeCustomization(
                                                             new AutoMoqCustomization(),
                                                             new T())))
        {
        }
    }

    public class AutoMoqDataAttribute : AutoDataAttribute
    {
        public AutoMoqDataAttribute()
            : base(() => new Fixture()
                       .Customize(
                                  new AutoMoqCustomization()))
        {
        }
    }
}

My test that works fine and uses the customization registration:

    [Theory]
    [AutoMoqData<CalenderControllerCustomization>]
    public async Task Test_GetAllEvents_ClientIsCalledCorrectlyAndReturnsCorrectData(
        IFixture fixture,
        [Frozen] Mock<ICalendarServiceClient> calendarServiceClientMock,
        CalendarController sut)
    {
        // Arrange
        var startDate = fixture.Create<DateTimeOffset>();
        var endDate = fixture.Create<DateTimeOffset>();

        // Act
        var eventList = await sut.GetAllEvents(startDate, endDate);

        // Assert
        eventList.Events.Count.Should().Be(3); // fixture always create 3 of lists here
        calendarServiceClientMock.Verify();
    }

THE PROBLEM:

Now I want to override the setup with my own data returned.

    [Theory]
    [AutoMoqData<CalenderControllerCustomization>]
    public async Task Test_GetAllEvents_ClientIsCalledCorrectlyAndReturnsCorrectData_Overridden(
        IFixture fixture,
        [Frozen] Mock<ICalendarServiceClient> calendarServiceClientMock,
        CalendarController sut)
    {
        // Arrange
        var startDate = fixture.Create<DateTimeOffset>();
        var endDate = fixture.Create<DateTimeOffset>();

        var result = fixture.Build<EventList>()
                            .With(x => x.Events, fixture.CreateMany<Event>(5).ToList())
                            .Create();

        // override client mock setup:
        calendarServiceClientMock.Setup(m => m.GetEvents(It.IsAny<DateTime>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>()))
                                 .ReturnsAsync(fixture.Build<EventList>()
                                                      .With(x => x.Events, fixture.CreateMany<Event>(5).ToList())
                                                      .Create()).Verifiable();

        // Act
        var eventList = await sut.GetAllEvents(startDate, endDate);

        // Assert
        eventList.Events.Count.Should().Be(5); // FAILS - IT GETS 3 AND NOT 5
        calendarServiceClientMock.Verify(); // THIS FAILS TOO IF I SET 3 IN ABOVE
    }

I have tried with and without the Frozen attribute.

What am I missing here? I don't want to call my calendarcontroller builder as the point is to avoid boilerplate. Do I need a registration of the client or something else I'm missing

(maybe related to Override Autofixture customization setup but using moq and not n-subtitute. Could not get this to work even with correct order of parameters)

Thanks for reading!

Morten_564834
  • 1,479
  • 3
  • 15
  • 26

1 Answers1

0

With the current way you customize the fixture I don't think you can. By creating a custom builder and instantiating the mock by hand you've basically painted yourself in a corner.

To get AutoFixture to control the instances you need in the test, you must allow it to generate them. Here's a possible solution.

The root of your SUT is the controller. Let's say it receives as a parameter an ICalendarService that's always the concrete implementation CalendarService.

To tell AutoFixture that the implementation is always a concrete type you can relay the resolved type to the concrete implementation.

fixture.Customizations.Add(new TypeRelay(typeof(ICalendarService), typeof(CalendarService)));

Next the service takes as a constructor parameter an abstraction that's ICalendarClient which is resolved as the wrapper but only for the service. This means you have to identify the constructor parameter and relay the request to the wrapper type.

fixture.Customizations.Add(new FilteringSpecimenBuilder(
    new FixedRequestRelay(typeof(CalendarClientWrapper)),
    new YourConstructorParameterSpecification()));

The YourConstructorParameterSpecification is a IRequestSpecification implementation that identifies a request as a parameter of type ICalendarClient that belongs to the constructor of type CalendarService.

FixedRequestRelay is a simple ISpecimenBuilder that always resolves the same predefined request from ISpecimenContext instead of the received one. Here's a very basic implementation of it.

public record FixedRequestRelay(object Request) : ISpecimenBuilder
{
    public object Create(object request, ISpecimenContext context)
        => context.Resolve(this.Request);
}

Next you have your wrapper that also takes as a parameter an abstraction of type ICustomerClient. However this instance you want to be resolved as a mock that you can control from the test. Since you use AutoFixure.AutoMoq you can leave this one as is and let AutoMoq generate a mock for it.

Let's say this is the resulting customization.

public class CalendarCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customizations.Add(new TypeRelay(typeof(ICalendarService), typeof(CalendarService)));
        fixture.Customizations.Add(new FilteringSpecimenBuilder(
            new FixedRequestRelay(typeof(CalendarClientWrapper)),
            new AndRequestSpecification(
                new ParameterSpecification(typeof(ICalendarClient), "client"),
                new ParameterMemberSpecification(
                    new DeclaringTypeSpecification(
                        new ExactTypeSpecification(typeof(CalendarService)))))));
    }
}

By using it you can write a test like this.

[Theory, MyData]
public async Task Foo(
    [MinLength(5)] Event[] events,
    [Frozen] Mock<ICalendarClient> clientMock,
    CalendarController controller,
    DateTime start, DateTime end)
{
    clientMock
        .Setup(x => x.GetAllEvents(It.IsAny<DateTime>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>()))
        .ReturnsAsync(events);

    var actual = await controller.GetAllEvents(start, end, default);

    Assert.Equal(events, actual);
}
Andrei Ivascu
  • 1,172
  • 8
  • 17