Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test Ninject ConstructorArguments using MOQ objects?

I have been doing my first Test Driven Development project recently and have been learning Ninject and MOQ. This is my first attempt at all this. I've found the TDD approach has been thought provoking, and Ninject and MOQ have been great. The project I am working on has not particularly been the best fit for Ninject as it is a highly configurable C# program that is designed to test the use of a web service interface.

I have broken it up into modules and have interfaces all over the shop, but I am still finding that I am having to use lots of constructor arguments when getting an implementation of a service from the Ninject kernel. For example;

In my Ninject module;

Bind<IDirEnum>().To<DirEnum>()

My DirEnum class;

public class DirEnum : IDirEnum
{
    public DirEnum(string filePath, string fileFilter, 
        bool includeSubDirs)
    {
        ....

In my Configurator class (this is the main entry point) that hooks all the services together;

class Configurator
{

    public ConfigureServices(string[] args)
    {
        ArgParser argParser = new ArgParser(args);
        IDirEnum dirEnum = kernel.Get<IDirEnum>(
            new ConstructorArgument("filePath", argParser.filePath),
            new ConstructorArgument("fileFilter", argParser.fileFilter),
            new ConstructorArgument("includeSubDirs", argParser.subDirs)
        );

filePath, fileFilter and includeSubDirs are command line options to the program. So far so good. However, being a conscientious kind of guy, I have a test covering this bit of code. I'd like to use a MOQ object. I have created a Ninject module for my tests;

public class TestNinjectModule : NinjectModule
{
    internal IDirEnum mockDirEnum {set;get};
    Bind<IDirEnum>().ToConstant(mockDirEnum);
}

And in my test I use it like this;

[TestMethod]
public void Test()
{
    // Arrange
    TestNinjectModule testmodule = new TestNinjectModule();
    Mock<IDirEnum> mockDirEnum = new Mock<IDirEnum>();
    testModule.mockDirEnum = mockDirEnum;
    // Act
    Configurator configurator = new Configurator();
    configurator.ConfigureServices();
    // Assert

    here lies my problem! How do I test what values were passed to the
    constructor arguments???

So the above shows my problem. How can I test what arguments were passed to the ConstructorArguments of the mock object? My guess is that Ninject is dispensing of the ConstuctorArguments in this case as the Bind does not require them? Can I test this with a MOQ object or do I need to hand code a mock object that implements DirEnum and accepts and 'records' the constructor arguments?

n.b. this code is 'example' code, i.e. I have not reproduced my code verbatim, but I think I have expressed enough to hopefully convey the issues? If you need more context, please ask!

Thanks for looking. Be gentle, this is my first time ;-)

Jim

like image 586
JBowen Avatar asked Jun 07 '11 18:06

JBowen


1 Answers

There are a few problems with the way you designed your application. First of all, you are calling the Ninject kernel directly from within your code. This is called the Service Locator pattern and it is considered an anti-pattern. It makes testing your application much harder and you are already experiencing this. You are trying to mock the Ninject container in your unit test, which complicates things tremendously.

Next, you are injecting primitive types (string, bool) in the constructor of your DirEnum type. I like how MNrydengren states it in the comments:

take "compile-time" dependencies through constructor parameters and "run-time" dependencies through method parameters

It's hard for me to guess what that class should do, but since you are injecting these variables that change at run-time into the DirEnum constructor, you end up with a hard to test application.

There are multiple ways to fix this. Two that come in mind are the use of method injection and the use of a factory. Which one is feasible is up to you.

Using method injection, your Configurator class will look like this:

class Configurator
{
    private readonly IDirEnum dirEnum;

    // Injecting IDirEnum through the constructor
    public Configurator(IDirEnum dirEnum)
    {
        this.dirEnum = dirEnum;
    }

    public ConfigureServices(string[] args)
    {
        var parser = new ArgParser(args);

        // Inject the arguments into a method
        this.dirEnum.SomeOperation(
            argParser.filePath
            argParser.fileFilter
            argParser.subDirs);
    }
}

Using a factory, you would need to define a factory that knows how to create new IDirEnum types:

interface IDirEnumFactory
{
    IDirEnum CreateDirEnum(string filePath, string fileFilter, 
        bool includeSubDirs);
}

Your Configuration class can now depend on the IDirEnumFactory interface:

class Configurator
{
    private readonly IDirEnumFactory dirFactory;

    // Injecting the factory through the constructor
    public Configurator(IDirEnumFactory dirFactory)
    {
        this.dirFactory = dirFactory;
    }

    public ConfigureServices(string[] args)
    {
        var parser = new ArgParser(args);

        // Creating a new IDirEnum using the factory
        var dirEnum = this.dirFactory.CreateDirEnum(
            parser.filePath
            parser.fileFilter
            parser.subDirs);
    }
}

See how in both examples the dependencies get injected into the Configurator class. This is called the Dependency Injection pattern, opposed to the Service Locator pattern, where the Configurator asks for its dependencies by calling into the Ninject kernel.

Now, since your Configurator is completely free from any IoC container what so ever, you can now easily test this class, by injecting a mocked version of the dependency it expects.

What is left is to configure the Ninject container in the top of your application (in DI terminology: the composition root). With the method injection example, your container configuration would stay the same, with the factory example, you will need to replace the Bind<IDirEnum>().To<DirEnum>() line with something as follows:

public static void Bootstrap()
{
    kernel.Bind<IDirEnumFactory>().To<DirEnumFactory>();
}

Of course, you will need to create the DirEnumFactory:

class DirEnumFactory : IDirEnumFactory
{
    IDirEnum CreateDirEnum(string filePath, string fileFilter, 
        bool includeSubDirs)
    {
        return new DirEnum(filePath, fileFilter, includeSubDirs);
    }        
}

WARNING: Do note that factory abstractions are in most cases not the best design, as explained here.

The last thing you need to do is to create a new Configurator instance. You can simply do this as follows:

public static Configurator CreateConfigurator()
{
    return kernel.Get<Configurator>();
}

public static void Main(string[] args)
{
    Bootstrap():
    var configurator = CreateConfigurator();

    configurator.ConfigureServices(args);
}

Here we call the kernel. Although calling the container directly should be prevented, there will always at least be one place in your application where you call the container, simply because it must wire everything up. However, we try to minimize the number of times the container is called directly, because it improves -among other things- the testability of our code.

See how I didn't really answer your question, but showed a way to work around the problem very effectively.

You might still want to test your DI configuration. That's very valid IMO. I do this in my applications. But for this, you often don't need the DI container, or even if your do, this doesn't mean that all your tests should have a dependency on the container. This relationship should only exist for the tests that test the DI configuration itself. Here is a test:

[TestMethod]
public void DependencyConfiguration_IsConfiguredCorrectly()
{
    // Arrange
    Program.Bootstrap();

    // Act
    var configurator = Program.CreateConfigurator();

    // Assert
    Assert.IsNotNull(configurator);
}

This test indirectly depends on Ninject and it will fail when Ninject is not able to construct a new Configurator instance. When you keep your constructors clean from any logic and only use it for storing the taken dependencies in private fields, you can run this, without the risk of calling out to a database, web service or what so ever.

I hope this helps.

like image 89
Steven Avatar answered Jan 02 '23 19:01

Steven