.NET and me Coding dreams since 1998!

13Feb/080

Design for testability – Dependency Injection Frameworks (Part 4)

I've been away a while (mostly due to vacation time), but I am back and switching gears from NET foundations to TDD subjects. Although I have prepared material for another "How-To Rhino mock" post I would first like to extend my blog post series on how to build software in a way which enables effective TDD.

Therefore, today's post will continue the Design For Testability (DFT) blog posts in which I already babbled about how to design for testability by effectively utilizing the dependency injection pattern on a simple starting example containing mixed DAL code, business code and cross-cutting concerns code:

My next couple of posts in DFT series will cover the same example from the perspective of using a dependency injection framework (which is based on the service locator design pattern):

  • Part 4 - Design for testability - Dependency Injection (DI) frameworks  (this post)
  • Part 5 - Design for testability - Castle Windsor dependency injection framework
  • Part 6 - Design for testability - StructureMap (v2.0) dependency injection framework
  • Part 7 - Design for testability - ObjectBuilder (v1) dependency injection framework

Also, I plan to cover upcoming revisions of DI frameworks as soon they are available somewhere for download (they are unavailable today)

  • Part 8 - Design for testability - Structure Map (2.5) - once it is available for review
  • Part 9 - Design for testability - Unity Dependency Injection Container   - once it is available for review

Setting the stage

At the end of the exploration of the dependency injection based solution (part 3), we ended up with a solution based on Registry design pattern in which we have a DALRepository static class exposing the singleton instance of CompanyManager class which has an internal property UserManager implementing a poor man's dependency injection of the UserManager instance. UserManager also has a UserProvider internal property which  (by default) gets injected with an instance of UserProvider. Something like this:

image

Testability design criteria was achieved by using the InternalsVisibleTo attribute and exposing the internal property setters to unit fixture assemblies so the test methods could easily inject mocked instances of the UserManager/UserProvider to decouple the test from unwanted dependencies. The test therefore can look like this:

 

        
	[Test]
        public void GetActiveUsers_TestCaseOfZeroUsers()
        {
            IUserManager userManager = 
		mockRepository.DynamicMock<IUserManager>();
            Expect.Call(userManager.NumberOfUsersActiveInLast10Days(null))
                .IgnoreArguments()
                .Return(0);
            mockRepository.ReplayAll();

            DALRepository.CompanyManager.UserManager = userManager;
            IList<int> results = DALRepository.CompanyManager.GetActiveUsers();
            Assert.IsTrue(results.Count == 0);
        }

As we can see in line 12, to set the CompanyManager to use UserManager mock, all we needed to do is to set the UserManager property to the appropriate value.

What's wrong with this?

Well, nothing is especially wrong with this code because we have code open for testing, but encapsulated to the developer using the code. In most cases I consider this an acceptable solution - it is not too complete, and does not rely on any supporting code.

But, in the real world, one class often has multiple dependencies even if it doesn't violate the SoC (Separation of Concerns). To support that with our code, we would have to implement multiple internal properties for the class, implement the lazy load, handle threading issues, etc. That would lead to a lot of code noise which we then have to hide in regions. The same story then repeats in other components, which multiplying the amount of code noise.

Another potential problem is that, to support the poor man's injection, we would have to reference the assembly containing the type. This could lead to the increasing risk of cyclical references (especially if the interfaces are located in a separate assembly).

From OOP purist perspective, setting attributes of "production" assemblies just to support testing needs feels "smelly". Adding a new test assembly would require adding a new attribute to the tested assembly.

Summarized: IMHO, while there are not a a lot of problems with the dependency injection pattern, dependency injection frameworks are a simple solution to these problems.

One thing all DI frameworks have in common

They are all based on the service locator pattern, which replaces the direct dependencies between the types with a dependency to well-known-object which, in turn, is capable of finding and instantiating the appropriate types. The following two diagrams show the essence of this pattern:

image

With tight coupling, class A has to refer directly to an instance of Service A.

With the Service Locator pattern, Class A is asking the Service Locator to "find" Service A "from somewhere" and return an instance to Class A.

Some configuration, a little bit of magic and registration

As we can guess from previous picture, the service locator has to have some way of knowing where to find Service A and Service B. Alternatively, the service location can provide a way for Service A and Service B to register themselves. Usually DI frameworks provide 3 ways for this: a configuration file, attribute decoration or manual registration.

XML configuration files

You can use an XML file to define the dictionary of service contracts and service implementations.

Something like this:

<configuration>
	<configSections>
		<section name="DIFrameworkSection" 
			type="DIFramework.SectionHandler, DIFramework" />
	</configSections>

	<DIFrameworkSection>
		<mappings>
			<mapping key="serviceA.Mapping" 
				type="Component.ServiceA, Component" />
			<mapping key="serviceB.Mapping" 
				interface="Component.IServiceA, Component"  
				type="Component.ServiceA, Component" />
		</mappings>
	</DIFrameworkSection>
</configuration>

As we can see in this example, we are adding a new configuration section into the configuration file. This section defines a list of "mappings" which the service locator uses to instantiate the appropriate service.

Usually, DI  frameworks offer two ways of defining mappings:

    • Mapping Definition 1: key - type definition  (ServiceA)

      where the type contains the name of class and the assembly containing the implementation of that class.

A sample call to the service locator in this case would look like this:

ServiceA service=ServiceLocator.GetInstance<ServiceA>();

    • Mapping Definition 2: key - interface - type definition (ServiceB)

      where we add an interface mapping so the service locator can be called with just the interface information provided. This decouples the service locator from a concrete implementation of the class.

A sample call to the service locator in this case would look like this:

IServiceB service=
	ServiceLocator.GetInstance<IServiceB>("serviceB.Mapping");

Attribute magic

Besides the configuration files, DI frameworks usually offer another way of enabling the service locator to automatically discover the services it should instantiate.

The concept is very simple: all the services are marked with an attribute and the DI framework performs reflexive inspection of the types in the given assemblies and registers these mappings. This happens once per application life cycle, during the DI framework's initialization sequence. No configuration files are needed.

Here's an example of what service locator attribute definition looks like (in pseudo code)

[MappingClassAttribute("serviceA.mapping")]
public class ServiceA
{
    ...
}


[MappingAbstractionAttribute("serviceB.abstraction.mapping")]
public class IServiceB
{
    ...
}


[MappingClassAttribute("serviceB.mapping")]
public class ServiceB: IServiceB
{
    ...
}

As we can see from the example above, we have once again defined the mapping to include a concrete implementation of both ServiceA and ServiceB and an abstract interface for IServiceB.

During the init sequence, there's a call to something like this

ServiceLocator.ScanAssemblies();

This causes the Service Locator to recursively walk through the project assemblies and collect of all the concrete and abstract entities marked with appropriate attributes.

The usage is exactly the same as in case of configuration file:

ServiceA serviceA=ServiceLocator.GetInstance<ServiceA>();

IServiceB serviceB=ServiceLocator.GetInstance<IServiceB>("serviceB.mapping");

Manual registration

During the creation of unit test methods, we usually need to swap a "real" service with the mocked instance containing the values needed for unit test execution. We can do this by having two different configuration files: production and test one. Alternatively, we could just have a production configuration file which includes overrides for test methods that require services.

The DI frameworks factory class exposing static method which can be used to define new service locator mappings (or override existing ones).

        
	[Test]
        public void ServiceA_TestXYZ()
        {
            IServiceA serviceA = mockRepository.DynamicMock<IServiceA>();
            
            /* setup values of mocked service
             * ....*/

            ServiceLocator.RegisterService<IServiceA>(serviceA);
            mockRepository.ReplayAll();

            /* perform unit test */
        }

Notice that, in line 9, we have set up the ServiceLocator to return a mocked instance of the type implementing IService which the tested code would then use inside of the test. This looks similar to the dependency injection solution at the beginning of this post, which is not surprising as we have just replaced a custom implementation of dependency injection design pattern with a pre-built dependency injection framework.

What's next?

After this general introduction to DI frameworks concepts, in next part of this Design for Testability series I will give a "How To" overview of Castle Windsor DI Framework (continuing with the same example I have used thus far). I will also highlight the advantages and disadvantages of Windsor compared to other DI frameworks and show some sample unit tests using Windsor.

So, stay tuned :)

[edited by cbeauvais]

Comments (0) Trackbacks (0)

No comments yet.


Leave a comment

No trackbacks yet.