2
votes

Preface: My default mode of operation is using an IoC container and constructor injection. This makes testing with mocked dependencies trivial.

I am starting to develop an IntelliJ plugin and I want to make use of inversion of control. Since this is a plugin there isn't really an option of a container (right?) so I suppose I need to use a Service Locator pattern.

How do I test using mocks with the Service Locator pattern?

The best way that I can think of is to use an interface for my locator, set it in the default constructor of each service using a static getter, and have a setter so that I can set a mocked locator. It would look something like this:

public class MyService {
    private IServiceLocator locator;

    public MyService() {
        setLocator(ServiceLocator.locator());
    }

    public void setLocator(IServiceLocator locator) {
        this.locator = locator;
    }
}

Now I can mock the IServiceLocator and set that on MyService in my test. I can then expect a call like locator.dependency1() and make it return a mocked dependency.

My main issue with this approach is the locator setter that is only there to support testing. Is there a better way?

2

2 Answers

2
votes

First off I would suggest reading the excellent "Working Effectively With Legacy Code" which has a bunch of patterns for dealing with stuff like this. Although you're writing new code, you're bound by some of the same restrictions as legacy code.

For this, an easier way might be to provide a second protected constructor that accepts your dependencies explicitly and have the no-arg constructor use the defaults you want. So something like the following:

 public class MyService {
    private Dependency1 dep1;
    private Dependency2 dep2;

    protected MyService(Dependency1 dep1, Dependency2 dep2) {
        this.dep1 = dep1;
        this.dep2 = dep2;
    }

    public MyService() {
         this(new ConcreteDependency1(), new ConcreteDependency2());
    }
}

You can also add an annotation to make it clear this is for testing, i.e. Guava's @VisibleForTesting

1
votes

You can do that in way you described. There are other options that comes into my mind:

You can use package-private access for service locator field

Now if you put your test class in the same package as service - it can directly manipulate fields inside tested class - so it is easy to mock service locator.

You can use features of testing/mocking library to inject service locator

For example Mockito (https://code.google.com/p/mockito/) can inject private fields int two ways:

  • Whitebox class - but this is not best idea since you have to know name of field that you want to mock (change of field name will break test), and you need to execute Withebox.setInternalState method manualy
  • @InjectMocks annotation - You create service locator field annotated with @Mock annotation, and service field annotated with @InjectMocks and mockito will use service locator field and injects it into service automatically.

You can use dependency injection mechanism that not depends on containers

For example check Google Guice library: https://code.google.com/p/google-guice/

You can use "manually" dependency injection

DI is pattern - DI containers/frameworks makes this pattern easy to implement, but you can always do it yourself with factory classes (but it takes more time). Here is nice talk about DI that covers "manual" DI: https://www.youtube.com/watch?v=RlfLCWKxHJ0

It is difficult to say which option is best for you. If you want to stick with service locator - I'd recomend Mockito. If you want DI - I'd recommend Google Guice. Personaly, I think that service locator is more like anti-pattern.