How to validate Ninject ConstructorArguments using MOQ objects?

I recently did my first test development project and studied Ninject and MOQ. This is my first attempt at all of this. I found that the TDD approach was provoked, and Ninject and MOQ were great. The project I'm working on is not particularly suitable for Ninject, because it is a custom C # program designed to test the use of the web service interface.

I broke it down into modules and had interfaces all over the store, but I still find that I have to use a lot of constructor arguments when getting the service implementation from the Ninject kernel. For instance:

In my Ninject module;

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

My class is DirEnum;

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

In my class, Configurator (this is the main entry point) that connects all 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 for the program. So far, so good. However, being a bona fide guy, I have a test covering this bit of code. I would like to use an MOQ object. I 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 as follows:

 [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 to check which arguments were passed to the design objects of the mock object? I assume Ninject throws ConstuctorArguments in this case, since Bind does not require them? Can I verify this using the MOQ object, or do I need to pass the code to the layout of the object that implements DirEnum and accepts and writes constructor arguments?

nb this code is an "example" of code, i.e. I did not reproduce my code verbatim, but I think I expressed enough to hope to convey the problems? If you need more context, ask!

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

Jim

+6
source share
1 answer

There are several problems with how you developed the application. First of all, you call the Ninject kernel directly from your code. This is called a service locator pattern and is considered an antivirus pattern . This greatly simplifies testing your application, and you are already experiencing it. You are trying to mock the Ninject container in the unit test, which complicates the situation.

Then you enter the primitive types ( string , bool ) in the constructor of your DirEnum type. I like how MNrydengren states this in the comments:

accept compile-time dependencies through constructor parameters and run-time dependencies using the Parameters method

It's hard for me to guess what this class should do, but since you insert these variables that change at run time into the DirEnum constructor, you get a complex test application.

There are several ways to fix this. Two that come to mind are using the injection method and using the factory. Which one is possible is up to you.

Using the injection method, 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 factory, you will need to define a factory that knows how to create new types of IDirEnum :

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

Now your Configuration class may 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 dependencies are inserted into the Configurator class. This is called the dependency nesting pattern , the opposite of the Locator service pattern, where Configurator queries its dependencies by calling Ninject in the kernel.

Now, since your Configurator completely free of any IoC container, which is always the case, you can now easily test this class by introducing the mock version of the dependency that it expects.

It remains to configure the Ninject container at the top of the application (in DI terminology: root directory ). In the example of the method implementation, the configuration of your container will remain the same, with the factory example, you will need to replace the line Bind<IDirEnum>().To<DirEnum>() as follows:

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

Of course, you will need to create a DirEnumFactory :

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

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

The last thing you need to do is create a new instance of Configurator . You can simply do it like this:

 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 core. Although calling a container directly should be prevented, your application will always have at least one place where you call the container, simply because it needs to connect everything. However, we try to minimize the number of container calls directly, as it improves - among other things - the verifiability of our code.

See how I really did not answer your question, but showed a way to effectively solve the problem.

You might want to check the DI configuration. This is a very relevant IMO. I do this in my applications. But for this, you often do not need a DI container, or even if you do this, this does not mean that all your tests should have a dependency on the container. This ratio should exist only for tests that themselves test the DI configuration. Here is the test:

 [TestMethod] public void DependencyConfiguration_IsConfiguredCorrectly() { // Arrange Program.Bootstrap(); // Act var configurator = Program.CreateConfigurator(); // Assert Assert.IsNotNull(configurator); } 

This test is indirectly dependent on Ninject, and it will not work when Ninject cannot create a new instance of Configurator . When you keep your constructors clean of any logic and use it only to store accepted dependencies in private fields, you can run this without risking accessing a database, web service or something.

Hope this helps.

+15
source

Source: https://habr.com/ru/post/889985/


All Articles