28

Using Guice, is it a good practice to get a new injector in each JUnit test class, as each test class should be independant?

Alexis Dufrenoy
  • 11,784
  • 12
  • 82
  • 124

7 Answers7

44

In case anyone stumbles upon this question and wants to see how to get Guice annotations working from unit tests, extend your tests from a base class like the one below and call injector.injectMembers(this);

public class TestBase {
    protected Injector injector = Guice.createInjector(new AbstractModule() {
        @Override
        protected void configure() {
            bind(HelloService.class);
        }
    });

    @Before
    public void setup () {
        injector.injectMembers(this);
    }
}

Then your test can get an injected HelloService like this

public class HelloServiceTest extends TestBase {
    @Inject
    HelloService service;

    @Test
    public void testService() throws Exception {
       //Do testing here
    }
}
Ray
  • 40,256
  • 21
  • 101
  • 138
Joe Abrams
  • 1,124
  • 11
  • 16
  • 1
    You should note to `injectMembers` to the classes you want to test and need injection, and not just to `this` which is the tester class. – Mahdi Aug 20 '16 at 23:38
  • Should it be `HelloServiceTest`, not `HelloServletTest` and ` HelloService service;` not `HelloServlet servlet;`? I'm assuming so and edited your answer. – Ray Dec 21 '17 at 15:26
  • ```TestBase``` should be ```abstract``` ? – wilmol Oct 03 '19 at 21:23
36

You should really avoid using Guice in unit tests as each test should be small enough that manual DI is manageable. By using Guice (or any DI) in unit tests you are hiding away a warning that your class is getting too big and taking on too many responsibilities.

For testing the bootstrapper code and integration tests then yes create a different injector for each test.

b4hand
  • 9,550
  • 4
  • 44
  • 49
Michael Lloyd Lee mlk
  • 14,561
  • 3
  • 44
  • 81
  • 2
    +1 ... after using guice injection in all test I now feel the need to revert this decision as tests consume a lot time with Guice.createInjector :( – Karussell Apr 17 '13 at 12:09
  • 11
    I do not agree. With Guice you can use @Inject and inject fields with no setters or constructors. It is more readable. So manual dependency in such case should be what? I prefer use Injector than manual Reflection API because it first comes in mind to me. – Michał Króliczek Apr 16 '14 at 11:32
  • 6
    I never inject directly to field without setters. I virtually never use setter injection. Both of which I find ugly and hide the classes requirements from users of said class. I try to only use ctor injection. By using Guice (or any DI) in unit tests you are hiding away a warning that your class is getting to big and taking on too many responsibilities. – Michael Lloyd Lee mlk Apr 16 '14 at 13:44
  • 4
    Do you tend to write "shallow" unit tests which mock the test subject's immediate dependencies? I find that if you write "full-stack" tests with real storage etc., it can be cumbersome to manually create a large portion of your dependency tree. Don't want to get into a debate over which testing approach is better though. – Daniel Lubarov Jan 17 '15 at 00:11
  • 1
    Both. Unit tests are shallow. Integration tests are full stack using the DI framework. – Michael Lloyd Lee mlk Jan 19 '15 at 15:14
  • 4
    There is not a "better" there is "better for THIS use case". – Michael Lloyd Lee mlk Jan 19 '15 at 15:14
  • 2
    +1 tests should be dead simple, you shouldn't have to look in another module to see how your code is being configured. I second the motion that "By using Guice (or any DI [framework]) in unit tests you are hiding away a warning that your class is getting to big and taking on too many responsibilities" – Jason Nov 05 '15 at 10:59
  • 7
    What about when the JUnit framework is used to run integration tests? – Dan Gravell Feb 22 '17 at 12:29
  • I did not use one. – Michael Lloyd Lee mlk Feb 23 '17 at 17:17
13

I think using DI will make unit test code more simple, I always Use DI for unit test and also for integration test.

Without DI everything feels hard to code. Either using Guice Inject or Spring Autowired. like my test code bellow:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "/application-context.xml")
public class When_inexists_user_disabled {
    @Autowired
    IRegistrationService registrationService;

    private int userId;

    @Before
    public void setUp() {
        Logger.getRootLogger().setLevel(Level.INFO);
        Logger.getLogger("org.springframework").setLevel(Level.WARN);
        BasicConfigurator.configure();

        userId = 999;
    }

    @Test(expected=UserNotFoundException.class)
    public void user_should_have_disabled() throws UserNotFoundException {
        registrationService.disable(userId);
    }

}
Adi Sembiring
  • 5,798
  • 12
  • 58
  • 70
  • 1
    Personally I think this harder to work out as I need to look in the app context file to find out what IRegistrationService is being used, if it is taking any mocks or stubs and how they are set up. If a test feels too hard to code manually then it is a sign that you *may* be testing too much or your object *may* require too much to get going. – Michael Lloyd Lee mlk Apr 12 '11 at 12:06
  • @mlk its no where near as bad with annotation config since you can setup everything you want including mocks within a single [at]Configuration bean, which you can make as an inner class. – AnthonyJClink Dec 13 '13 at 05:32
7

This depends on which version of JUnit is being used. Our teams have used Junit4 successfully and are now looking into JUnit5.

In Junit5 we use extensions.

    public class InjectionPoint implements BeforeTestExecutionCallback {

        @Override
        public void beforeTestExecution(ExtensionContext context) throws Exception {

            List<Module> modules = Lists.newArrayList(new ConfigurationModule());

            Optional<Object> test = context.getTestInstance();

            if (test.isPresent()) {
                RequiresInjection requiresInjection = test.get().getClass().getAnnotation(RequiresInjection.class);

                if (requiresInjection != null) {
                    for (Class c : requiresInjection.values()) {
                        modules.add((Module) c.newInstance());
                    }
                }

                Module aggregate = Modules.combine(modules);
                Injector injector = Guice.createInjector(aggregate);

                injector.injectMembers(test.get());
                getStore(context).put(injector.getClass(), injector);
            }

        }

        private Store getStore(ExtensionContext context) {
            return context.getStore(Namespace.create(getClass()));
        }

    }

Then each test uses the RequiresInjection annotation, which can accept an array of inner modules to aggregate, or none to use the default.

    @RequiresInjection
    public class Junit5InjectWithoutModuleTest {

        @Inject
        private TestEnvironment environment;

        @Test
        public void shouldAccessFromOuterModule() {
            assertThat(environment).isNotNull();
        }

    }

And here's the annotation:

    @ExtendWith(InjectionPoint.class)
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
    public @interface RequiresInjection {

        Class<? extends Module>[] values() default {};

    }

JUnit5 is still new to me, so I may be looking into templates, but so far the Extensions seem to do the trick.

With JUnit4 we use a similar approach, except that the injection takes place within the createTest method of our custom test runner, and then each test implements a RequiresInjection interface that has a "getModule" method.

I should probably give a shout out to TestNG as well, as Guice support is built right in. Usage is as simple as this:

@Guice({SomeObjectModule.class})
    public class MyTest {

        @Inject
        SomeObject someObject;    

    }
Chs
  • 131
  • 1
  • 6
6

Take a look at Guice Berry.

I won't recommend using it now (documentation is really terrible), but looking at their approach can make you think clear about how DI should be done in jUnit.

Sotomajor
  • 1,386
  • 11
  • 15
  • If you do decide to use GuiceBerry, you can make `@Provides` functions that also have the `@TestScoped` annotation ( http://stackoverflow.com/a/37979254/345648 ) (or `bind(YourClass.class).in(TestScoped.class);` ). This tells Guice to create only one instance per test, as opposed to @Singleton which would make components reused across tests, or not having an annotation, which creates a new instance each time it's injected (could be multiple instances per test). – Alexander Taylor Jun 23 '16 at 00:54
2

I found AtUnit to be an excellent complement to Guice (it even deals with mock framework integration).

This makes the Unit Test classes extremely clear and concise (never see an Injector there) and, where appropriate, also lets you exercise your production bindings as part of your unit tests.

sxc731
  • 2,418
  • 2
  • 28
  • 30
1

I suggest this framework I have recently written Guice-Behave.

It is very simple, with two annotations you can run the test in the same context of your application.

You can define your mocks inside the Guice module and in this way it is very easy to re-use them.

Giulio Caccin
  • 2,962
  • 6
  • 36
  • 57
Alessandro
  • 19
  • 3