Using Guice, is it a good practice to get a new injector in each JUnit test class, as each test class should be independant?
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.
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
}
}
injectMembers
to the classes you want to test and need injection, and not just to this
which is the tester class. –
Sharpsighted HelloServiceTest
, not HelloServletTest
and ` HelloService service;` not HelloServlet servlet;
? I'm assuming so and edited your answer. –
Cofer TestBase
should be abstract
? –
Outreach 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.
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;
}
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.
@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 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.
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.
© 2022 - 2024 — McMap. All rights reserved.
@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 ) (orbind(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