Question

I'm new to AutoFixture and am trying to create a friendly extension on my test context for the less TDD-inclined devs in the team. Here is the code:

public class HomeController : Controller
{
    private readonly ISomeService _someService;

    public HomeController(ISomeService someService)
    {
        _someService = someService;
    }

    public ActionResult Index()
    {
        _someService.SomeMethod();
        return View("Index");
    }
}

public class ControllerContext<T> where T : Controller
{
    protected static T ControllerUnderTest;
    private static IFixture _fixture;

    public ControllerContext()
    {
        _fixture = new Fixture().Customize(new AutoMoqCustomization());
        _fixture.Customize<ControllerContext>(c => c.Without(x => x.DisplayMode));
        ControllerUnderTest = _fixture.Create<T>();
    }

    protected static Mock<TDouble> For<TDouble>() where TDouble : class
    {
        //var mock = _fixture.Create<TDouble>();
        var mock = _fixture.Create<Mock<TDouble>>();
        return mock;
    }
}

So the extension is the For method - When I inspect ControllerUnderTest which has an injected 'ISomeService' it has an instance injected just fine, and it definitely calls the method I am asserting against. When I inspect the mock created in the 'For' method it appears to be the same version as the one injected to the controller, but it won't Verify!

public class EXAMPLE_When_going_to_home_page : ControllerContext<HomeController>
{
    Because of = () =>
    {
        ControllerUnderTest.Index();

    };

    It should_do_something = () =>
    {
        //This throws a 'Invocation was not performed'
        For<ISomeService>().Verify(x => x.SomeMethod());
    };

    Establish context = () =>
    {

    };
}

I am struggling to find any examples of someone doing something similar, I know I am definitely doing something stupid here but in my head this test should pass?

Was it helpful?

Solution

Create creates a new anonymous instance every time, unless you froze (via .Freeze<T>() or AutoFixture.Xunit's [Frozen]) an instance. That means that the value that is injected into HomeController is different from the one returned by For.

There are several possible solutions, all of which ultimately will involve Freezing the value or Injecting the one to use.

One example would look like this:

public class ControllerContext<T> where T : Controller
{
    private static Lazy<T> _controllerFactory;
    private static IFixture _fixture;

    public ControllerContext()
    {
        _fixture = new Fixture().Customize(new AutoMoqCustomization());
        _fixture.Customize<ControllerContext>(c => c.Without(x => x.DisplayMode));
        _controllerFactory = new Lazy<T>(() => _fixture.Create<T>());
    }

    protected static Mock<TDouble> For<TDouble>() where TDouble : class
    {
        var mock = _fixture.Freeze<Mock<TDouble>>();
        return mock;
    }

    protected static T ControllerUnderTest
    {
        get { return _controllerFactory.Value; }
    }
}

public class EXAMPLE_When_going_to_home_page : ControllerContext<HomeController>
{
    static Mock<ISomeService> SomeService;

    Because of = () =>
    {
        SomeService = For<ISomeService>();
        ControllerUnderTest.Index();
    };

    It should_do_something = () =>
    {
        //This throws a 'Invocation was not performed'
        SomeService.Verify(x => x.SomeMethod());
    };

    Establish context = () =>
    {

    };
}

The important point of this changed version is that first Freeze is called on the service mock and only after that the anonymous instance of the controller is created. Because of the way the For method is now used, you should probably rename it to GetService.

OTHER TIPS

You'll ultimately end up in a world of pain if you go down the road of having static state as a way of managing the interaction between the services and the SUT. One reason is for example that unit tests should be parallelizable (e.g. xUnit.net v2 but ultimately all test frameworks as it just makes sense)

You can add Customizations to AutoFixture to allow natural creation of MVC Controllers as needed and then it's just a matter of feeding in or Freezing customized dependencies as necessary.

I'd strongly suggest taking the time to change your structure of your tests to have AutoFixture creating the Controller declaratively - have a look at what's possible with AutoFixture.Xunit and use that to inform how you structure the test helpers you're using in your Specs.

(Some background - I've been around the houses with all this Spec stuff using SubSpec and ultimately ended up much happier with AutoFixture.Xunit - it's just simpler and more composable.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top