Guice injector in JUnit tests

62,453

Solution 1

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.

Solution 2

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
    }
}

Solution 3

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.

Solution 4

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);
    }

}

Solution 5

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;    

    }
Share:
62,453

Related videos on Youtube

Alexis Dufrenoy
Author by

Alexis Dufrenoy

I'm a french software engineer and software architect. I work mainly with Java (JavaEE, Spring), Javascript (Angular, ExtJS) and RDBMS (MySQL, PostSQL, Oracle, Sybase, Teradata). I also have knowledge in Typescript, C, C++, shellscript, social network application programming (Facebook, Opensocial, Twitter), image processing, payment solutions implementation (Paypal) and more. About personal projects: I started the french wikibook on programming (yeah, back in 2004 or something). I was very involved at different levels in Wikipedia and sister projects for 5 years, until 2008. Consumed most of my spare time, then. But it was worth it. I'm also a real free software maniac. Free as in free speech... My other center of interest are photography (I got a Canon 7D I'm having real fun with. I took the picture of a polar fox I'm using as an avatar with my older 350D), cooking (I'm french, after all :-) and seeing museums, especially art (I can't grow tired of the Marmottan Museum and its unique Monet paintings collection). My 2 mother tongues are french and german, and apparently I'm not all so bad in english... :-). Beside France, I worked in Germany and Switzerland (where I also lived).

Updated on July 09, 2022

Comments

  • Alexis Dufrenoy
    Alexis Dufrenoy almost 2 years

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

  • Michael Lloyd Lee mlk
    Michael Lloyd Lee mlk about 13 years
    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.
  • Karussell
    Karussell about 11 years
    +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 :(
  • AnthonyJClink
    AnthonyJClink over 10 years
    @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.
  • Michał Króliczek
    Michał Króliczek about 10 years
    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.
  • Michael Lloyd Lee mlk
    Michael Lloyd Lee mlk about 10 years
    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.
  • Hartmut Pfarr
    Hartmut Pfarr over 9 years
    If I am right the last commit for AtUnit source base is in the year 2008.
  • Daniel Lubarov
    Daniel Lubarov over 9 years
    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.
  • Michael Lloyd Lee mlk
    Michael Lloyd Lee mlk over 9 years
    Both. Unit tests are shallow. Integration tests are full stack using the DI framework.
  • Michael Lloyd Lee mlk
    Michael Lloyd Lee mlk over 9 years
    There is not a "better" there is "better for THIS use case".
  • Jason
    Jason over 8 years
    +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"
  • Alexander Taylor
    Alexander Taylor almost 8 years
    If you do decide to use GuiceBerry, you can make @Provides functions that also have the @TestScoped annotation ( 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).
  • Mahdi
    Mahdi over 7 years
    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.
  • Dan Gravell
    Dan Gravell about 7 years
    What about when the JUnit framework is used to run integration tests?
  • Michael Lloyd Lee mlk
    Michael Lloyd Lee mlk about 7 years
    I did not use one.
  • Ray
    Ray over 6 years
    Should it be HelloServiceTest, not HelloServletTest and ` HelloService service;` not HelloServlet servlet;? I'm assuming so and edited your answer.
  • wilmol
    wilmol over 4 years
    TestBase should be abstract ?