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

Using Jersey's Dependency Injection in a Standalone application

我这里有接口​​

interface Idemo{
  public int getDemo(int i);
}

这是一种实现方式

class DemoImpl implements Idemo{
  @Override
  public int getDemo(int i){
    return i+10;
  }
}

还有一个 class 依赖于 Idemo

class Sample{
  @Inject
  Idemo demo;

  public int getSample(int i){
    return demo.getDemo(i);
  }
}

现在说我要测试样品 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.I 假设这是因为当执行到达指定绑定的行时,此测试 class 已经 created.But 我不是 sure.With Spring Autowired 而不是 jersey CDI 同样有效

如果样本是 resource/controller class 测试框架将创建它的一个实例而无需注入它但是是否可以测试任何其他非网络 class使用 Jersey DI ?

它与 Spring 一起工作的原因是测试 class 由 Spring 容器使用 @RunWith(SpringJUnit4ClassRunner.class) 管理。 runner 会将所有托管对象注入到测试对象中。 JerseyTest 不是这样管理的。

如果你愿意,你可以创建自己的运行器,但你需要了解一下 HK2(Jersey 的 DI 框架)的工作原理。看看 the documentation。一切都围绕着ServiceLocator。在独立的情况下,您可能会看到 bootstrap DI 容器

类似的内容
ServiceLocatorFactory factory = ServiceLocatorFactory.getInstance();
ServiceLocator locator = factory.create(null);
ServiceLocatorUtilities.bind(locator, new MyBinder());

然后要获取服务,请执行

Service service = locator.getService(Service.class);

在测试 class 的情况下,我们不需要获得对服务对象的任何访问权限,我们可以简单地注入测试对象,使用 ServiceLocator:

locator.inject(test);

上面,test 是在我们的自定义运行程序中传递给我们的测试 class 实例。这是自定义运行器的示例实现

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();
    }
}

我遇到了同样的情况,但是在 运行 的上下文中,一些集成测试需要我的应用程序已经定义的一些单例。

我发现的技巧如下。您只需要创建一个普通测试 class 或使用 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);

    }

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

可以找到对 DropwizardAppRule 创建的 ServiceLocator 的引用 here