3

I am trying to unit test a class that uses factory injection. I have a class that instantiates two copies of the same object (with different config) to control hardware. I'm trying to test the behaviour of the classes while simulating the hardware calls.

I've injected a set of factory delegates into the constructors so that the class can instantiate the hardware classes as required. However I just can't work out how to control or create factory methods within the Autofac.Extras.Moq package. It seems that this functionality isn't supported in the package.

I'm looking for an equivalent call to :

mock.Provide<IHWController>(//created by factory delegate)

I want to create a specific mock object with behaviour, based on the parameters used to instantiate the HWcontroller. Is what I'm trying to do even possible?

class SystemUnderTest
{
    SystemUnderTest(Ia a, Ib b, Ic c, 
         /** 15 other things **/ 
         Func<Func<Uri, IHwController>, HwType, IHwManager> HwManagerFactory, 
         Func<Uri, IHwController> HwControllerFactory)
    {
    }

}

class HwManager()
{
    public Func<HwType, Func<Uri, HwController>, HwManager> Factory; 

    public HwManager(HwType type, Func<Uri, HwController> ControlFactory)
    {  
        //Constructor
    }
}

The code I'm unit testing creates Managers of controllers. The controller is the hardware layer, but I'm testing complex (coupled) behaviour inside the manager. Therefore, I'm trying to work out how to mock the Func<Uri, HwController> ControlFactory so that it returns my setup mock objects so that I can probe the behaviour of the manager.

My system under test creates a concrete instantiation of the HWManager. I realise in a perfect scenario I would test the HwManager and SUT separately, but I'm specifically testing the integration of the two components.

I'd like to configure the autofac to control the delegate factory. If this isn't possible, then I can manually setup the SUT, but then I don't get any value from the autofac helper.

Spence
  • 28,526
  • 15
  • 68
  • 103
  • Can you show some more code? – NWard Apr 15 '15 at 23:53
  • Regarding your latest update, the HwManagerFactory confuses me. The controller factory is clear in that it takes a Uri and produces an IHwController, but you're explicitly specifying that same controller factory in HwManagerFactory. Are you saying the Func isn't an injected parameter for HwManagerFactory? I would expect Func where the Func is injected e.g. SystemUnderTest(Func HwManagerFactory, Func HwControllerFactory). – ScheuNZ Apr 16 '15 at 03:50
  • The controller has variables established at runtime based on user config. The manager has config also determined by the runtime and can be dynamically recreated by the code if it fails (to kill sockets etc.). Ergo I have an object created dynamically, which has injected into it a factory to create more objects. Regardless, I'm asking if I've missed something, or this is a missing feature in Autofac.Extras.Moq. – Spence Apr 16 '15 at 04:21
  • https://github.com/autofac/Autofac/issues/636 FYI if anyone would like to see this functionality – Spence Apr 16 '15 at 06:26

2 Answers2

2

I usually just create a Func that returns my mocked instance e.g.

var controller = mock.Provide<IHWController>();
var manager = new HwManager(something, (uri) => controller);

If the factory is expressing "given a Uri I will provide a controller", the lamda in my example above satisfies that statement.

As an aside, you should express your factories using interfaces, not concrete classes. It makes it a lot harder to unit test when the factories are producing concrete classes instead of interfaces (which can always be mocked) e.g.

// IHwController is the product of the factory instead of HwController
public HwManager(HwType type, Func<Uri, IHwController> ControlFactory)
ScheuNZ
  • 911
  • 8
  • 19
  • This won't work because the HwManager itself is being created by the system under test. If I do it as you recommend, then I need to manually create all the other dependencies for the HwController. If I was working one level done, this would be viable. – Spence Apr 16 '15 at 03:27
  • Also I was stripping confidential info, the func<> constructors always pass the Icomponent, not the concrete instantiation, missed the "I". – Spence Apr 16 '15 at 03:28
  • Okay, I'll definately need your example expanded then sorry. I can't quite join the dots to see how you're setting the test up. – ScheuNZ Apr 16 '15 at 03:32
0

If anyone gets stuck on this in the future, the key is to create a func that matches the constructor func, customised to provide the appropriate mocks on each invokation (e.g. via an internal counter).

You can then use the mock.Provide() syntax, providing a func that matches the constructor func with your created func above. This will then be invoked correctly allowing you to control the mocks appropriately.

Spence
  • 28,526
  • 15
  • 68
  • 103