Guice injector in JUnit tests
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
}
}
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);
}
}
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;
}
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.