在使用 Guice 时,每个 JUnit 测试类中获取一个新的注入器是一个好的实践吗?因为每个测试类都应该是独立的。
在使用 Guice 时,每个 JUnit 测试类中获取一个新的注入器是一个好的实践吗?因为每个测试类都应该是独立的。
如果有人偶然遇到了这个问题,并想要了解如何从单元测试中使用Guice注释,则可以从类似下面的基类扩展你的测试,并调用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);
}
}
然后您的测试可以像这样获取一个注入的HelloService
public class HelloServiceTest extends TestBase {
@Inject
HelloService service;
@Test
public void testService() throws Exception {
//Do testing here
}
}
在单元测试中,应该避免使用Guice,因为每个测试应该足够小,以便手动依赖注入(DI)可以管理。通过在单元测试中使用Guice(或任何DI),您正在隐藏一个警告,即您的类变得太大并承担了太多责任。
对于测试引导程序代码和集成测试,则可以为每个测试创建不同的注入器。
我认为使用DI
会使单元测试代码更简单,我总是在单元测试和集成测试中使用 DI。
没有 DI 一切都感觉很难编写。无论是使用Guice Inject或Spring Autowired
,就像下面的测试代码:
@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);
}
}
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()));
}
}
然后每个测试都使用RequiresInjection注解,该注解可以接受一个内部模块数组来聚合,或者不使用任何参数来使用默认值。
@RequiresInjection
public class Junit5InjectWithoutModuleTest {
@Inject
private TestEnvironment environment;
@Test
public void shouldAccessFromOuterModule() {
assertThat(environment).isNotNull();
}
}
以下是注释:
@ExtendWith(InjectionPoint.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
public @interface RequiresInjection {
Class<? extends Module>[] values() default {};
}
JUnit5 对我来说还比较新,所以我可能会研究模板,但目前扩展似乎很管用。
在 JUnit4 中,我们使用类似的方法,不同之处在于注入发生在自定义测试运行器的 createTest 方法中,然后每个测试实现 RequiresInjection 接口,该接口有一个 "getModule" 方法。
我也应该向 TestNG 致敬,因为它内置了 Guice 支持。使用起来就像这样简单:
@Guice({SomeObjectModule.class})
public class MyTest {
@Inject
SomeObject someObject;
}
看一下Guice Berry。
我不建议现在使用它(文档真的很糟糕),但是看看他们的方法可以让你清楚地思考在jUnit中应该如何进行DI。
@TestScoped
注释的@Provides
函数(http://stackoverflow.com/a/37979254/345648)(或`bind(YourClass.class).in(TestScoped.class);`)。这告诉Guice每个测试只创建一个实例,而不是像@Singleton那样使组件在测试之间重复使用,或者没有注释,每次注入时都会创建一个新实例(可能每个测试有多个实例)。 - Alexander Taylor
injectMembers
应用于你想要测试和需要注入的类,而不仅仅是测试类中的this
。 - MahdiHelloServiceTest
,而不是HelloServletTest
,以及HelloService service;
而不是HelloServlet servlet;
。我假设是这样的,并编辑了你的答案。 - RayTestBase
应该是抽象的(abstract)
吗? - wilmol