tags:

views:

468

answers:

4

I have started using Guice to do some dependency injection on a project, primarily because I need to inject mocks (using JMock currently) a layer away from the unit test, which makes manual injection very awkward.

My question is what is the best approach for introducing a mock? What I currently have is to make a new module in the unit test that satisfies the dependencies and bind them with a provider that looks like this:

public class JMockProvider<T> implements Provider<T> {
    private T mock;

    public JMockProvider(T mock) {
        this.mock = mock;
    }

    public T get() {
        return mock;
    }
}

Passing the mock in the constructor, so a JMock setup might look like this:

    final CommunicationQueue queue = context.mock(CommunicationQueue.class);
    final TransactionRollBack trans = context.mock(TransactionRollBack.class);
    Injector injector = Guice.createInjector(new AbstractModule() {
        @Override
        protected void configure() {
            bind(CommunicationQueue.class).toProvider(new JMockProvider<QuickBooksCommunicationQueue>(queue));
            bind(TransactionRollBack.class).toProvider(new JMockProvider<TransactionRollBack>(trans));
        }
    });
    context.checking(new Expectations() {{
        oneOf(queue).retrieve(with(any(int.class)));
        will(returnValue(null));
        never(trans);
    }});
    injector.getInstance(RunResponse.class).processResponseImpl(-1);

Is there a better way? I know that AtUnit attempts to address this problem, although I'm missing how it auto-magically injects a mock that was created locally like the above, but I'm looking for either a compelling reason why AtUnit is the right answer here (other than its ability to change DI and mocking frameworks around without changing tests) or if there is a better solution to doing it by hand.

+6  A: 

You shouldn't need to inject mocks though a DI framework. I'm using Guice and JMock quite successfully and my unit tests don't reference anything Guice related. I only use mocks and pass in null where applicable.

DI will allow the injection and construct of the dependencies of the current class, so If you want to add a mocked out class (which effectively stops the dependency graph) you just need to pass it in. Misko Hevery stated in one of the Google Tech Talks that unit tests should be littered with new's and null's because their scope is localized to the individual unit test method - I have to agree with him.

Is there a reason to need to use Guice in the tests, that is if they are not functional / integration tests?

Wouldn't the test work if it excluded the injector? Couldn't you refactor your test to something like:

final CommunicationQueue queue = context.mock(CommunicationQueue.class);
final TransactionRollBack trans = context.mock(TransactionRollBack.class);

context.checking(new Expectations() {{
    oneOf(queue).retrieve(with(any(int.class)));
    will(returnValue(null));
    never(trans);
}});

RunResponse r = new RunResponse(queue, trans); // depending on the order
r.processResponseImpl(-1);
gpampara
In the specific example, you are quite right that DI isn't needed for mocking (if I made the constructor package private, I guess), but from here I have dependencies that need to be injected two steps away the test code (RunResponse gets a class name out of the queue and instantiates it), so I was looking at Guice to do field injection for this purpose.
Yishai
I see. Nothing stops you from using Guice in the tests, but if possible, I'd suggest to try and avoid it (unless you are testing on a functional / integration level)The problem with that is that you are violating the Law of Demeter. Your current class should only be worried about it's immediate dependencies and transitive dependencies should not be taken into account. In the unit tests for the current classes dependencies you do exactly the same and only consider immediate dependencies. Such a pattern makes your code cleaner and more DI friendly :)
gpampara
+1  A: 

Hi Yishai,

From what you said, it sounds like you are not doing a real unit-test. When doing unit-test for a class, you only focus on a single class. When you execute your unit-test, the execution should only exercise the subject class to be tested and avoid running methods from related classes.

JMock/Easymock are tools to help achieve unit-testing your class. They are not useful in integration test.

In the context of unit-testing, Guice is overlapped with JMock/Easymock. Guice forces users to pollute their code with all kind of annotations (such as @Inject) to achieve the same result as JMock/Easymock. I really strongly disagree with Guice in the way they help in testing. If developers find themselves to use Guice instead of JMock/Easymock, they properly either not doing a real unit-test or they try to test controller classes.

Testing controller class should not be done at unit-test level but at integration test level. For integration test, using Jakarta Cactus is much helpful. At integration test, developers should have all dependencies available and hence, there is no need to use Guice.

As a conclusion, I don't see any reasons to use Guice in testing a product. Google Guice might be useful in different contexts but testing.

Thang
Thanks Thang, that is basically where I am now, in most cases I don't use Guice for testing. I have found Guice to drive a better design, so it is definitely earning its keep. However, in my case the principle of "a unit test tests only one class" isn't possible, because one class needs to lead to instantiating another through a framework. It is complicated why this is the case, but there is no good way (as in a way without other downsides that affect the production robustness) around it - I have tried.
Yishai
A: 

Yishai, I have to correct that driving a better design should be credited to Dependency Injection pattern introduced by Martin Fowler. Guice is a tool to help if your codes are already followed DI pattern. Guice doesn't contribute anything to your better coding. Guice is solving DI in ad-hoc manner (at annotation level), so it should not be considered to be used as DI framework either. This is the big difference between xml based DI container by Spring and annotation based DI container by Guice.

I spent some time to read your code posted at the beginning (I usually don't) just to understand why you said unit test your class isn't possible. I finally understand the problem you are facing. From what I see is at unit-test level, you attempted to mimic the execution flow of the piece of code when it runs inside JEE server. That's the reason you got stuck. I don't know what the incentive here is but I am afraid that your effort of preparing all of these environmental dependencies is going to be wasted :(

Writing unit test is to test a "business logic" which is independent from whatever technology you are using. There might be an aside requirement stating that this business logic must be executed within Quality of Service (QoS) environment. This requirement is "technical requirement". In your example, CommunicationQueue and TransactionRollBack are mostly functioning to satisfy the "technical requirement". The situation is your "business requirement" is being surrounded by all of these environmental units. What you should do is refactoring your code so that your "business requirement" is in separated methods which have no dependencies. Then you write unit-test for these methods.

I still consider your are doing functional testing. Doing functional testing at unit-test level is very time-consuming and it is not worth to do such thing either. I hope it helps

Thang
With regards to Guice, I think the annotation approach is better for reasons of improved static checking and reduced duplication, but I agree that Guice is a tool towards a design concept. With regards to why I need this, it is too long to fit in a comment, but in very short, the code requires that the container be in the middle, so I am trying to unit test the code without the container (call on one side to get back the result on the other), but they have different dependencies, must be in different classes, and each must be instantiated via framework code.
Yishai
A: 

Yishai, sounds like you need AOP to walk around these dependencies. AspectJ and AspectWerkz are both useful resources. However, AspectWekz has a very nice feature of weaving aspect classes at runtime using JVM hotswap technology. Of course, these engineering effort should only apply at unit testing level.

I have put lot of effort in resolving dependencies when doing unit testing. Tools such as JMock, EasyMock, JEE micro-container, AspectJ, AspectWerkz, Spring IoC can be used to solve DI issue. None of these tools are driving developers putting testing awareness in their production codes. Too bad that Guice is violating the concept of keeping your codes clean from any sorts of testing awareness. And I don't count it as a tool to be used in my unit-test.

I hope I have given you enough reasons to take @Inject annotation off your production code.

Thang
Thang, I don't know why you think that Guice makes your code any more "testing aware" than Spring, AspectJ or AspectWekz but you haven't given me any reasons, just alternatives and assertions.
Yishai
Thang