José F. Romaniello

Las aventuras y desventuras de un codificador.

This a common question, and the answer usually is that this kind of code is harder to test. Really? For me this is not so true.

This is an example test:

[Test]
public void do_something_save_the_object_properly()
{
    var daoMock = new DaoMock();

    //initializing my servicelocator stub.
    ServiceLocatorStub.Create().AddInstance<IDao>(daoMock);

    //this component use service locator internally
    var component = new AComponent(); 
    component.DoSomething();

    Assert.AreEqual("hey", daoMock.WasCalledWith); 

}

The implementation of DoSomething look as follows:

public void DoSomething()
{
    ServiceLocator.Current.GetInstance<IDao>().Save("hey");
}

The “DaoMock” implementation is not so usefull, but:

public class DaoMock : IDao
{
    public string WasCalledWith { get; private set; }
    public void Save(string anObject)
    {
        WasCalledWith = anObject;
    }
}

And finally here is the ServiceLocatorStub:

public class ServiceLocatorStub : ServiceLocatorImplBase 
{
    private readonly IDictionary<Type, ICollection<object>> 
        registeredTypes = new Dictionary<Type, ICollection<object>>();

    private ServiceLocatorStub()
    {}

    public ServiceLocatorStub AddInstance<TService>(TService instance)
    {
        ICollection<object> instanceCollection;
        if(!registeredTypes.TryGetValue(typeof(TService), out instanceCollection))
        {
            instanceCollection = new List<object>();
            registeredTypes[typeof (TService)] = instanceCollection;
        }
        instanceCollection.Add(instance);
        return this;
    }

    public static ServiceLocatorStub Create()
    {
        var fakeServiceLocator = new ServiceLocatorStub();
        ServiceLocator.SetLocatorProvider(() => fakeServiceLocator);
        return fakeServiceLocator;
    }

    protected override object DoGetInstance(Type serviceType, string key)
    {
        return registeredTypes[serviceType].FirstOrDefault();
    }

    protected override IEnumerable<object> DoGetAllInstances(Type serviceType)
    {
        return registeredTypes[serviceType];
    }
}

The constructor of the class is private, you must call Create(), this will return a new instance and set this instance as the current service locator. Then you can add services (mocks or stubs) fluently!

You don't need to reference a concrete container, neither the implementation of the service locator for the container.

| More

4 comentarios:

Mauricio Scheffer dijo...

The thing is, this <span>ServiceLocatorStub is not a stub but an actual basic container. And if the code under test gets services by key, you would have to add that to the "stub", making it a more complete container. You could just as well have used Ninject or Windsor or any other container instead, with a ServiceLocator adapter, but that's not the point. The real problem is that the dependencies are not explicit. It's just easier and more maintainable to use real dependency injection. Mark Seeman explains it very well in this post.</span>

José F. Romaniello dijo...

I know where you go. I do use real dependency injection when it's possible.
There are certain cases where service location is better for me, such as a
DaoFactory or ControllerFactory.
On the other hand, I must admit that the example is not as good.

Personally, I prefer to inject IServiceLocator abstraction for such classes, rather than using global ServiceLocator.Current instance.

José F. Romaniello dijo...

<span>Yes, I've said that before, but sometimes you can't. An example of it, is when you don't control the creation of the instance.</span>

You will see a concrete example of this in my next post.

Publicar un comentario en la entrada