Guice injector in JUnit tests [closed]
Asked Answered
S

7

29

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

Shaun answered 12/4, 2011 at 10:39 Comment(0)
M
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.

Magistrate answered 13/4, 2011 at 12:0 Comment(1)
If you do decide to use GuiceBerry, you can make @Provides functions that also have the @TestScoped annotation ( https://mcmap.net/q/501158/-creating-a-custom-testscoped-guice-scope-for-the-duration-of-a-unit-test ) (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).Pedant
D
45

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
    }
}
Devi answered 7/3, 2016 at 6:14 Comment(3)
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.Sharpsighted
Should it be HelloServiceTest, not HelloServletTest and ` HelloService service;` not HelloServlet servlet;? I'm assuming so and edited your answer.Cofer
TestBase should be abstract ?Outreach
V
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.

Viridian answered 12/4, 2011 at 10:57 Comment(9)
+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 :(Mishamishaan
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.Albumose
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.Viridian
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.Flowery
Both. Unit tests are shallow. Integration tests are full stack using the DI framework.Viridian
There is not a "better" there is "better for THIS use case".Viridian
+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"Imbroglio
What about when the JUnit framework is used to run integration tests?Theisen
I did not use one.Viridian
I
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);
    }

}
Ite answered 12/4, 2011 at 11:37 Comment(2)
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.Viridian
@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.Kermitkermy
R
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;    

    }
Rawdin answered 14/5, 2018 at 17:17 Comment(0)
M
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.

Magistrate answered 13/4, 2011 at 12:0 Comment(1)
If you do decide to use GuiceBerry, you can make @Provides functions that also have the @TestScoped annotation ( https://mcmap.net/q/501158/-creating-a-custom-testscoped-guice-scope-for-the-duration-of-a-unit-test ) (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).Pedant
Z
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.

Zoom answered 13/4, 2011 at 16:41 Comment(1)
If I am right the last commit for AtUnit source base is in the year 2008.Stenotypy
E
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.

Externalize answered 8/2, 2014 at 14:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.