Specialized Lazy support in AutoFixture for deferred instantiation

Viewed 50

I am using AutoData with AutoMoqCustomization (the InlineAutoMockData attribute sets this up for me) to auto-inject constructor parameters for my SUT. One particular SUT has some business logic that happens in a constructor which requires the mocked & frozen injected dependencies to be set up prior to the constructor executing. My workaround right now is as follows (using Moq + NUnit3 + Autofixture):

[Test,
 InlineAutoMockData(DbPermissionsMode.ElevatedDefault, true),
 InlineAutoMockData(DbPermissionsMode.ReducedNoViewServerState, false)]
public void Enumerator_mappings_are_correct(
    DbPermissionsMode permissionMode, bool expectedBool,
    [Frozen] Mock<IProductConfigProvider> configProvider,
    IFixture fixture)
{
    configProvider.Setup(x => x.GetDatabasePermissionsMode(It.IsAny<bool>()))
                  .Returns(permissionMode);

    var vm = fixture.Build<CredentialsControlViewModel>()
                    .OmitAutoProperties()
                    .Create();

    vm.UseElevatedPermissions.Should().Be(expectedBool);
}

In this test, CredentialsControlViewModel invokes IProductConfigProvider.GetDatabasePermissionsMode() in its constructor. This workaround allows me to perform setup on the configProvider mock instance prior to constructing the vm variable using the injected fixture.

What would be more ideal to clean this up is something like this, which in my mind should be functionally the same:

[Test,
 InlineAutoMockData(DbPermissionsMode.ElevatedDefault, true),
 InlineAutoMockData(DbPermissionsMode.ReducedNoViewServerState, false)]
public void Enumerator_mappings_are_correct(
    DbPermissionsMode permissionMode, bool expectedBool,
    [Frozen] Mock<IProductConfigProvider> configProvider,
    [NoAutoProperties] Lazy<CredentialsControlViewModel> vm)
{
    configProvider.Setup(x => x.GetDatabasePermissionsMode(It.IsAny<bool>()))
                  .Returns(permissionMode);

    vm.Value.UseElevatedPermissions.Should().Be(expectedBool);
}

Here, I no longer use the IFixture directly. Instead, I want Lazy<> to behave as follows:

  1. Internally create the given type (CredentialsControlViewModel) exactly as it would have been created if I instead passed in the parameter to the test as CredentialsControlViewModel vm (i.e. do it through Autofixture).
  2. Propagate all of the attributes attached to Lazy<> to the type created by Lazy's factory function (append all of the customizations / specimen builders)
  3. In all reasonable aspects, act as if I had done [NoAutoProperties] CredentialsControlViewModel vm directly, just within a factory function provided to Lazy<> instead of done immediately.

As a pseudo-code analogy, I would expect it to set up a Lazy<> from my earlier example like this:

var fixture = new Fixture().Customize(new AutoMoqCustomization { ConfigureMembers = true });
var lazy = new Lazy<CredentialsControlViewModel>(() =>
{
    return fixture.Create<CredentialsControlViewModel>();
});

I see that there's already a LazyRelay, but it doesn't do this, I think. I believe it just forwards the factory functionality to Func<>, but I still get a MissingMemberException from Lazy because there is no default constructor. This blog post says that this should work, so I'm not sure what is going on.

I am happy to customize this if needed, I just don't know how to do that properly. AutoFixture customizations have a steep learning curve.

Could someone recommend a way to customize AutoFixture to get this behavior for Lazy? Note that I want this solution to be generic. I don't want to have to do a fixture.Register() for every closed type of Lazy<>.

0 Answers
Related