我这里有一个界面
interface Idemo{
public int getDemo(int i);
}
这是一个实现
class DemoImpl implements Idemo{
@Override
public int getDemo(int i){
return i+10;
}
}
并且有一个类依赖于Idemo
class Sample{
@Inject
Idemo demo;
public int getSample(int i){
return demo.getDemo(i);
}
}
现在说我要测试Sample class
public class SampleTest extends JerseyTest {
@Inject
Sample s;
@Override
protected Application configure() {
AbstractBinder binder = new AbstractBinder() {
@Override
protected void configure() {
bind(Demo.class).to(Idemo.class);
bind(Sample.class).to(Sample.class); //**doesn't work**
}
};
ResourceConfig config = new ResourceConfig(Sample.class);
config.register(binder);
return config;
}
@Test
public void test_getSample() {
assertEquals(15, s.getSample(5)); //null pointer exception
}
}
这里没有创建Sample实例,并且s保持为null。我想这是因为当执行到达指定绑定的行时,已经创建了这个测试类。但是我不确定。而是使用Spring Autowired而不是球衣CDI同样的作品
如果Sample是资源/控制器类,测试框架会创建它的实例而不需要注入它,但是可以使用Jersey DI测试任何其他非Web类吗?
答案 0 :(得分:6)
它与Spring一起使用的原因是测试类由Spring容器使用@RunWith(SpringJUnit4ClassRunner.class)
进行管理。运行器将所有托管对象注入测试对象。 JerseyTest
不是以这种方式管理的。
如果你愿意,你可以创建自己的跑步者,但你需要了解HK2(泽西岛的DI框架)的工作原理。看看the documentation。一切都围绕着ServiceLocator
。在独立版本中,您可能会看到类似这样的内容来引导DI容器
ServiceLocatorFactory factory = ServiceLocatorFactory.getInstance();
ServiceLocator locator = factory.create(null);
ServiceLocatorUtilities.bind(locator, new MyBinder());
然后要获得服务,请执行
Service service = locator.getService(Service.class);
对于测试类,我们不需要获得对服务对象的任何访问权限,我们只需使用ServiceLocator
注入测试对象:
locator.inject(test);
上面,test
是在我们的自定义运行器中传递给我们的测试类实例。以下是自定义运行器的示例实现
import java.lang.annotation.*;
import org.glassfish.hk2.api.*;
import org.glassfish.hk2.utilities.*;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.*;
public class Hk2ClassRunner extends BlockJUnit4ClassRunner {
private final ServiceLocatorFactory factory = ServiceLocatorFactory.getInstance();
private Class<? extends Binder>[] binderClasses;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public static @interface Binders {
public Class<? extends Binder>[] value();
}
public Hk2ClassRunner(Class<?> cls) throws InitializationError {
super(cls);
Binders bindersAnno = cls.getClass().getAnnotation(Binders.class);
if (bindersAnno == null) {
binderClasses = new Class[0];
}
}
@Override
public Statement methodInvoker(FrameworkMethod method, final Object test) {
final Statement statement = super.methodInvoker(method, test);
return new Statement() {
@Override
public void evaluate() throws Throwable {
ServiceLocator locator = factory.create(null);
for (Class<? extends Binder> c : binderClasses) {
try {
ServiceLocatorUtilities.bind(locator, c.newInstance());
} catch (InstantiationException | IllegalAccessException ex) {
throw new RuntimeException(ex);
}
}
locator.inject(test);
statement.evaluate();
locator.shutdown();
}
};
}
}
在跑步者中,为每个测试方法调用methodInvoker
,因此我们为每个被称为测试方法的对象创建一组新的对象。
这是一个完整的测试用例
@Binders({ServiceBinder.class})
@RunWith(Hk2ClassRunner.class)
public class InjectTest {
public static class Service {
@Inject
private Demo demo;
public void doSomething() {
System.out.println("Inside Service.doSomething()");
demo.doSomething();
}
}
public static class Demo {
public void doSomething() {
System.out.println("Inside Demo.doSomething()");
}
}
public static class ServiceBinder extends AbstractBinder {
@Override
protected void configure() {
bind(Demo.class).to(Demo.class);
bind(Service.class).to(Service.class);
}
}
@Inject
private Service service;
@Test
public void testInjections() {
Assert.assertNotNull(service);
service.doSomething();
}
}
答案 1 :(得分:0)
我遇到了同样的情况,但是在运行某些集成测试的环境中,需要我的应用程序已经定义了一些单例。
我发现的技巧如下。您只需要创建一个普通的测试类或使用DropwizardAppRule
就我而言,我正在编写一些集成测试时使用JUnit
。
public class MyIntegrationTest{
//CONFIG_PATH is just a string that reference to your yaml.file
@ClassRule
public static final DropwizardAppRule<XXXConfiguration> APP_RULE =
new DropwizardAppRule<>(XXXApplication.class, CONFIG_PATH);
}
@ClassRule
将启动您的应用程序,如here所述。那
意味着您可以访问应用程序需要启动的所有对象。在我的情况下,我需要使用@Inject
注释和@Named
public class MyIntegrationTest {
@ClassRule
public static final DropwizardAppRule<XXXConfiguration> APP_RULE =
new DropwizardAppRule<>(XXXAplication.class, CONFIG_PATH);
@Inject
@Named("myService")
private ServiceImpl myService;
}
运行它会将服务设置为null,因为@Inject不起作用,因为我们此时没有将bean放入引用的任何内容。这种方法很方便。
@Before
public void setup() {
ServiceLocator serviceLocator =((ServletContainer)APP_RULE.getEnvironment().getJerseyServletContainer()).getApplicationHandler().getServiceLocator();
//This line will take the beans from the locator and inject them in their
//reference, so each @Inject reference will be populated.
serviceLocator.inject(this);
}
这将避免在您的应用程序之外创建其他绑定器和配置。
可以找到ServiceLocator
创建的DropwizardAppRule
的参考here