在独立应用程序中使用Jersey的依赖注入

5

我这里有一个接口

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类。
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
  }
}

这里示例实例没有被创建,s保持为null。我认为这是因为当执行到绑定的那一行时,测试类已经被创建了。但我不确定。使用Spring Autowired而不是Jersey CDI可以解决相同的问题。

如果Sample是资源/控制器类,测试框架将创建它的实例,无需注入它,但是否可能使用Jersey DI测试任何其他非Web类?

2个回答

7
它能够与Spring一起使用的原因是测试类通过使用@RunWith(SpringJUnit4ClassRunner.class)由Spring容器管理。运行程序将把所有受管对象注入测试对象中。JerseyTest不会以这种方式进行管理。
如果您想要,可以创建自己的运行程序,但需要了解HK2(Jersey的DI框架)的工作原理。请查看文档。一切都围绕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();
            }
        };
    }
}

在Runner中,对于每个测试方法都会调用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();
    }
}

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 会像 这里 所说的那样启动您的应用程序。这意味着您将可以访问应用程序启动所需的所有内容和对象。在我的情况下,我需要访问我的服务的单例,我使用 @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);

    }

这将避免在您的应用程序之外创建其他绑定器和配置。

可以在此处找到DropwizardAppRule创建的ServiceLocator的引用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接