【问题标题】:How to unit test Jersey + Guice: ServiceLocator?如何对 Jersey + Guice:ServiceLocator 进行单元测试?
【发布时间】:2014-07-29 14:39:34
【问题描述】:

通过使用 HK2 的 guice-bridge,我设法将 Jersey 2.x 与 Guice 3.x 集成。

public class MyApp extends ResourceConfig {

    @Inject
    public MyApp(ServiceLocator serviceLocator) {
        packages("com.mycompany");

        ...

        GuiceBridge.getGuiceBridge().initializeGuiceBridge(serviceLocator);
        GuiceIntoHK2Bridge guiceBridge = serviceLocator.getService(GuiceIntoHK2Bridge.class);
        guiceBridge.bridgeGuiceInjector(GuiceContext.INJECTOR);
    }
}

但是现在我的泽西测试不再起作用了。

public abstract class AbstractJerseyTest extends JerseyTest {

    public AbstractJerseyTest() throws TestContainerException {
        super(new InMemoryTestContainerFactory());
    }

    protected Application configure() {
        new MyApp(); // ERROR: missing 'ServiceLocator'
    }

}

那么我在哪里可以得到ServiceLocator 用于我的单元测试?

【问题讨论】:

  • 您找到其他解决方案了吗?

标签: java jersey guice jersey-2.0 guice-3


【解决方案1】:

这是一个有效的解决方案。

关键是重写 JerseyTest 的 configureDeployment() 方法并通过传递 Application 特定的 ResourceConfig.class 来创建 DeploymentContext,而不是重写 configure() 方法并返回 ResourceConfig 实例,以便测试容器正确初始化 guice-bridge。

这适用于以下版本的 Jersey、Guice 和 HK2 guice-bridge

    <jersey.version>2.15</jersey.version>
    <jackson2.version>2.4.4</jackson2.version>
    <hk2.guice.bridge.version>2.4.0-b10</hk2.guice.bridge.version>
    <guice.version>4.0-beta5</guice.version>

1) 我的服务类

public interface MyService {
    public void hello();
}

2) 我的模拟服务实现

public class MyMockServiceImpl implements MyService{
    public void hello() {
        System.out.println("Hi");
    }
} 

3) 我的带有 Guice 注入服务的资源类

@Path("myapp")
public class MyResource {

    private final MyService myService;

    @Inject
    public MyResource(MyService myService) {
        this.myService = myService;
    }
}

4) 我的资源测试类

public class MyResourceTest extends JerseyTestNg.ContainerPerClassTest {

    @Override
    protected Application configure() {
        return null;
    }

    @Override
    protected DeploymentContext configureDeployment() {
        return DeploymentContext.builder(MyTestConfig.class).build();
    }

    // other test and setup/teardown methods
}

5) 资源配置类

static class MyTestConfig extends ResourceConfig {

    @Inject
    public MyTestConfig(ServiceLocator serviceLocator) {

        packages("com.myapp.rest");

        GuiceBridge.getGuiceBridge().initializeGuiceBridge(serviceLocator);

        GuiceIntoHK2Bridge guiceBridge = serviceLocator.getService(GuiceIntoHK2Bridge.class);
        guiceBridge.bridgeGuiceInjector(Guice.createInjector(new MyTestModule()));

    }
}

6) 我的 Guice 测试模块类

public class MyTestModule implements Module {

    @Override
    public void configure(Binder binder) {
            binder.bind(MyService.class)
               .to(MyMockServiceImpl.class);
    }

}

【讨论】:

    【解决方案2】:

    也许更简洁的方法是简单地使用Feature 并配置网桥那里而不是ResourceConfig

    public class GuiceFeature implements Feature {
    
        public void configure(FeatureContext context) {
            ServiceLocator serviceLocator = ServiceLocatorProvider.getServiceLocator(context);
            GuiceBridge.getGuiceBridge().initializeGuiceBridge(serviceLocator);
            GuiceIntoHK2Bridge guiceBridge = serviceLocator.getService(GuiceIntoHK2Bridge.class);
            guiceBridge.bridgeGuiceInjector(GuiceContext.INJECTOR);
        }
    }
    

    这可以像任何其他功能一样注册。只需register 或使用@Provider 扫描即可。

    请注意,ServiceLocatorProvider 仅适用于 Jersey 2.6 及更高版本。

    【讨论】:

    • 谢谢!这个解决方案效果非常好! :-) 它应该被标记为答案。
    • 虽然上面确实允许你注入由 Guice 创建的绑定,但它仍然不允许需要安装 GuiceServlet 的请求范围的对象。
    【解决方案3】:

    注意:我以前从未使用过泽西岛。但是,您不应该再拨打new MyApp() 了;否则 Guice 将无法工作。相反,我可能会尝试这样的事情:

    public abstract class AbstractJerseyTest extends JerseyTest {
        private final Module[] modules;
    
        public AbstractJerseyTest(Module... modules) throws TestContainerException {
            super(new InMemoryTestContainerFactory());
            this.module = modules;
        }
    
        protected Application configure() {
            Injector inj = Guice.createInjector(modules);
            return inj.getInstance(MyApp.class);
        }
    }
    
    public class ActualTest extends AbstractJerseyTest {
        private static class TestModule extends AbstractModule {
            @Override
            public void configure() {
                // Do your guice bindings here
            }
        }
    
        public ActualTest() throws TestContainerException {
            super(new TestModule());
        }
    }
    

    【讨论】:

    • 感谢您的回复。 MyApp 类需要使用 Jersey 的内部注入器 HK2 才能注入 ServiceLocator。所以这行不通:(
    【解决方案4】:

    我们在 Groovy 的帮助下使它工作:

    public class MemoryTestContainerFactory implements TestContainerFactory {
    
        private final Class<? extends Application> jaxrsApplicationClass;
    
        public MemoryTestContainerFactory(Class<? extends Application> jaxrsApplicationClass) {
            this.jaxrsApplicationClass = jaxrsApplicationClass;
        }
    
        @Override
        public TestContainer create(URI baseUri, DeploymentContext context) throws IllegalArgumentException {
            return new MemoryTestContainer(jaxrsApplicationClass, baseUri, context);
        }
    
        private static class MemoryTestContainer implements TestContainer {
    
            private final URI baseUri;
            private final ApplicationHandler appHandler;
            private final AtomicBoolean started = new AtomicBoolean(false);
    
            private static final Logger LOGGER = Logger.getLogger(MemoryTestContainer.class.getName());
    
            MemoryTestContainer(Class<? extends Application> jaxrsApplicationClass, URI baseUri, DeploymentContext context) {
                this.baseUri = UriBuilder.fromUri(baseUri).path(context.getContextPath()).build();    
                this.appHandler = new ApplicationHandler(jaxrsApplicationClass);
            }
    
            @Override
            public ClientConfig getClientConfig() {
                def provider = new InMemoryConnector.Provider(baseUri, appHandler) // private access (only works with Groovy)
                return new ClientConfig().connectorProvider(provider);
            }
    
            @Override
            public URI getBaseUri() {
                return baseUri;
            }
    
            @Override
            public void start() {
                if (started.compareAndSet(false, true)) {
                    LOGGER.log(Level.FINE, "Starting InMemoryContainer...");
                } else {
                    LOGGER.log(Level.WARNING, "Ignoring start request - InMemoryTestContainer is already started.");
                }
            }
    
            @Override
            public void stop() {
                if (started.compareAndSet(true, false)) {
                    LOGGER.log(Level.FINE, "Stopping InMemoryContainer...");
                } else {
                    LOGGER.log(Level.WARNING, "Ignoring stop request - InMemoryTestContainer is already stopped.");
                }
            }
        }
    }
    

    它不漂亮,但它有效。

    【讨论】:

      猜你喜欢
      • 2015-04-21
      • 2012-01-08
      • 1970-01-01
      • 2019-11-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-30
      • 2019-01-05
      相关资源
      最近更新 更多