Using Ioc For Unit Testing

Using IoC for Unit Testing

Generally speaking, a DI Container should not be necessary for unit testing because unit testing is all about separating responsibilities.

Consider a class that uses Constructor Injection

public MyClass(IMyDependency dep) { }

In your entire application, it may be that there's a huge dependency graph hidden behind IMyDependency, but in a unit test, you flatten it all down to a single Test Double.

You can use dynamic mocks like Moq or RhinoMocks to generate the Test Double, but it is not required.

var dep = new Mock<IMyDependency>().Object;
var sut = new MyClass(dep);

In some cases, an auto-mocking container can be nice to have, but you don't need to use the same DI Container that the production application uses.

How do i unit test a class that uses IoC container classes

The reason you are not able to test your class is because your class takes a dependency on your DI Container. This is an implementation of the Service Locator anti-pattern. It's an anti-pattern because:

the problem with Service Locator is that it hides a class' dependencies, causing run-time errors instead of compile-time errors, as well as making the code more difficult to maintain because it becomes unclear when you would be introducing a breaking change.

Instead, design your classes around

  • Constructor Injection in case the class is a component (a class that contains the applications behavior), where you inject the dependencies that a class directly needs through the constructor
  • Method Injection when the class is a data-centric object like an entity, which means the dependency is supplied to a the public method of such class, where the consuming class only uses that dependency, but not stores it.

Components are built-up by your DI Container and are registered in your Composition Root, while data-centric objects are newed up in code outside the Composition Root. In that case you need to pass along a dependency to an already constructed object.

In case you build and test a component, your code would typically look as follows:

public class ComponentA
{
private ClassName b;

public ComponentA(ClassName b)
{
this.b = b;
}

public void DoSomething()
{
// Do something with instance B
}
}

// Test class
public Class ATest
{
[test]
public void TestMethod()
{
// Arrange
B b = new FakeB();

var a = new ComponentA(b);

// Act
a.DoSomething();

// Assert
// Check whether be was invoked correctly.
}
}

In case you build and test a data-centric object that requires a dependency for one of its operations, your code would typically look as follows:

public class EntityA
{
public string Name { get; set; }
public int Age { get; set; }

public void DoSomething(ClassName b)
{
// Do something with instance B
}
}

// Test class
public Class ATest
{
[test]
public void TestMethod()
{
// Arrange
B b = new FakeB();

var a = new EntityA { Name = "Bert", Age = 56 };

// Act
a.DoSomething(b);

// Assert
// Check whether be was invoked correctly.
}
}

So to answer your initial question:

How do i unit test a class that uses IoC container classes

You don't. Your application code should not depend on the DI Container, because that leads to all kinds of complications such as being hard to test.

Best way of using IoC for production and Testing

Although passing the container through the constructor is better than making the container accessible as a singleton for the complete application, it is still a form of Service Locator (anti-pattern), and is not recommended. This has clear disadvantages:

  1. It makes your code much harder to follow.
  2. It makes it much less obvious when the Single Responsibility Principle is violated (since the class hides which things it depends on).
  3. It makes it much hard to test, since you need to pass a configured container and you need to look in the code to see what the test need.
  4. When requesting instances from the container directly from within every class, you will disable many features that IOC containers give you, because you don't allow the container to build up the object graph. Depending on the framework of choice, certain lifestyles and features like context based injection will not be usable.
  5. All your tests use a container instance, making your tests come complex, and all your test have a dependency on the DI framework, which makes it very expensive to switch to another framework when needed.

Just inject the dependencies into the constructor, never* the container itself.

*Exception to this rule is when such class is located inside the appliation's Composition Root. In that case it's not considered to be the Simple Locator pattern, because that class is simply an infrastructure component.

Mocking vs IoC container in unit testing

The purpose isn't to replace integration tests that will test modules from a higher level. Unit tests are intended to test a discrete class in isolation, mostly to confirm that the design and coding of that class is complete.

How to configure unit tests with an IoC container in ASP.NET?

Probably using the global application class for storing the service locator was not a good idea. Why don't you use the built-in ServiceLocator class? It is available from anywhere in the code and doesn't depend on global application / HttpContext.

Whether or not using the container in unit tests is another story. Personally I am not against it as long as you put stub implementations of your services into the container.

Edit: the way to configure your container using ServiceLocator:

    private void ConfigureUnity()
{
UnityServiceLocator locator = new UnityServiceLocator( ConfigureUnityContainer() );
ServiceLocator.SetLocatorProvider( () => locator );
}

private IUnityContainer ConfigureUnityContainer()
{
IUnityContainer container = new UnityContainer();

// this loads container's configuration, comment or uncomment
container.LoadConfiguration();

return container;
}

You can then access the container from within the locator like:

var container = ServiceLocator.Current.GetInstance<IUnityContainer>();

Can I use an IoC container in unit/integration tests?

When we are unit testing a class we are concerned with 'does the class do what we want it to do'.
Our starting point is a fully constructed instance; how we got there is not a unit testing question though it may be considered an integration testing question.

Say we have,

A
A(IB b, IC c)

B : IB
B(ID d)

C : IC

D : ID

Where:

IB is an interface for B,

IC is an interface for C, and

ID is an interface for D.

When unit testing A, the fact that B uses ID should be moot (if it is not then we should look at our interfaces. Having A access IB.D.xxx() is not good), all we need to do is provide A with some implementation (mocked/stubbed) of IB and IC.

As far as the unit tests for A are concerned, whether the A instance is created by hand or by a container is not important. We get the same object either way.

As long as we are passing in mocks as the first level dependencies then there is no saving when using a container over creating the object by hand. The saving only happens when we are creating object graphs using the IOC, but if we are doing this then we are into integration testing. This is not necessarily a bad thing but we need to be clear on our goals.

When unit testing the above we create unit testing for

 D
C
B (passing in a mocked/stubbed ID)
A (passing in mocked/stubbed IC and IB)

When unit testing A we do not need the correct answer from D to be passed through B up to A.

All we care is that the logic in A works as expected, say, A calls IB.x() with the parameters y and z and returns the result of IB.x(). If we are checking that we get the correct answer (say, one which depends on logic in D) then we are past unit testing and into integration testing.

Bottom Line

It does not matter whether or not the unit under test was created by an IOC container or by hand as long as we are properly isolating the unit under test. If we are using the container to create an object graph then the odds are good that we are into integration testing (and/or have problems with too much coupling between classes - A calling IB.D.xxx())

Mocking for Integration Tests

Caveat: Some of this following is dependent upon the IOC container in use. When using Unity, the last registration 'wins'. I do not know that this holds true for others.

In the minimalist system under question we have

A 
A (IB b)

B : IB

B is our 'leaf'. It talks to the outside world (say, reads from a network stream).
When Integration testing, we want to mock this.

For the live system, we set up the ServiceLocator using CreateContainerCore().
This includes the registration of the 'live' implementation of IB.

When executing integration tests that require a mocked version of IB we set up the container using CreateContainerWithMockedExternalDependencies() which wraps CreateContainerCore() and registering a mocked object for IB.

The code below is heavily simplified but the shape extends out to as many classes and dependencies as required. In practice, we have a base test class that aids setting up the service locator, mocking/stubbing classes, accessing the mocks for verification purposes and other house keeping (e.g.so that each test doesn't need to explicitly set the ServiceLocator provider)

[TestClass]
public class IocIntegrationSetupFixture {


[TestMethod]
public void MockedB() {

ServiceLocator.SetLocatorProvider(() => new UnityServiceLocator(CreateContainerWithMockedExternalDependencies()));

var a = ServiceLocator.Current.GetInstance<A>();
Assert.AreEqual("Mocked B", a.GetMessage());

}


[TestMethod]
public void LiveB() {

ServiceLocator.SetLocatorProvider(() => new UnityServiceLocator(CreateContainerCore()));

var a = ServiceLocator.Current.GetInstance<A>();
Assert.AreEqual("Live B", a.GetMessage());
}

private IUnityContainer CreateContainerCore() {
var container = new UnityContainer();
container.RegisterType<IB, B>(new ContainerControlledLifetimeManager());
return container;
}

private IUnityContainer CreateContainerWithMockedExternalDependencies() {
var container = CreateContainerCore();

var mockedB = new Mock<IB>();
mockedB.SetupGet(mk => mk.Message).Returns("Mocked B");
container.RegisterInstance<IB>(mockedB.Object);

return container;
}


public class A {

private IB _b;
public A(IB b) {
_b = b;
}

public string GetMessage() {
return _b.Message;
}

}

public interface IB {
string Message { get; }
}

private class B : IB {
public string Message {
get { return "Live B"; }
}
}

}

What should be the strategy of unit testing when using IoC?

You don't need DI container in unit tests because dependencies are provided through mock objects generated with frameworks such as Rhino Mocks or Moq. So for example when you are testing a class that has a dependency on some interface this dependency is usually provided through constructor injection.

public class SomeClassToTest
{
private readonly ISomeDependentObject _dep;
public SomeClassToTest(ISomeDependentObject dep)
{
_dep = dep;
}

public int SomeMethodToTest()
{
return _dep.Method1() + _dep.Method2();
}
}

In your application you will use a DI framework to pass some real implementation of ISomeDependentObject in the constructor which could itself have dependencies on other objects while in a unit test you create a mock object because you only want to test this class in isolation. Example with Rhino Mocks:

[TestMethod]
public void SomeClassToTest_SomeMethodToTest()
{
// arrange
var depStub = MockRepository.CreateStub<ISomeDependentObject>();
var sut = new SomeClassToTest(depStub);
depStub.Stub(x => x.Method1()).Return(1);
depStub.Stub(x => x.Method2()).Return(2);

// act
var actual = sut.SomeMethodToTest();

// assert
Assert.AreEqual(3, actual);
}

How to use IoC in unit tests properly?

I would say it depends on whether the injected items are concrete implementations or mock objects. If you are using concrete implementations, then you are likely to have state issues between tests if you initialise at the class level.

I typically reinitialise each of the dependencies per test, even the one concrete class (which mocks a service layer on top of a mock repository). When testing CRUD type functionality, it definitely helps to be able to reset to zero each test, despite the fact that it takes a little longer. Rather have a test that can be run correctly and reliably in isolation or in a list.



Related Topics



Leave a reply



Submit