使用Guice Injections进行单元测试JAX-RS / Jersey servlet

时间:2018-08-18 23:44:36

标签: java servlets guice jersey-2.0 jersey-test-framework

我有一个使用Jersey / JAX-RS进行Web服务(注释等)和Guice注入服务实现的应用程序。我真的不喜欢Guice直接与servlet一起工作的方式,我更喜欢Jersey方式,所以我不得不做些大惊小怪的事情,以使服务注入正常工作,因为Guice不会创建我的servlet类,而我没有不想处理HK2-Guice桥。为此,我创建了一个侦听器类(称为Configuration),该类在应用程序启动时在静态字段中设置了注入器,然后通过创建一个父类来手动实现每个servlet类中的注入,我的所有servlet都通过一个包含以下内容的构造函数进行扩展:

public MasterServlet() {
    // in order for the Guice @Inject annotation to work, we have to create a constructor
    // like this and call injectMembers(this) on all our injectors in it
    Configuration.getMyServiceInjector().injectMembers(this);
    Configuration.getDriverInjector().injectMembers(this);
}

我知道这有点hacky,但这在我的servlet中很好用。我可以在服务上使用Guice @Inject批注,并在命名的实现之间进行切换,等等。当我去设置单元测试时,问题就来了。我正在使用JerseyTest进行测试,但是对我的servlet运行测试会导致500的错误,而Guice则显示以下内容:

  

com.google.inject.ConfigurationException:Guice配置错误:   1)没有实现com.mycompany.MyService的绑定。     定位com.mycompany.MyService时       com.mycompany.servlet.TestGetServlet.service(TestGetServlet.java:21)上的字段     在定位com.mycompany.servlet.TestGetServlet

测试如下:

public class TestServletTest extends JerseyTest {

    @Test
    public void testServletFunctional() {
        final String response = target("/testget").request().get(String.class);
        assertEquals("get servlet functional", response);
    }

    @Before
    public void setup() {
        Configuration configuration = new Configuration();
        configuration.contextInitialized(null);
    }

    @Override
    protected Application configure() {
        return new ResourceConfig(TestGetServlet.class);
    }
}

您会注意到在setup方法中,由于我不能依靠测试容器(Grizzly)来创建它,因此我正在手动创建Configuration类(没有这两行,我会得到NullPointerExceptions)。有关此内容的更多信息。

这是正在测试的servlet:

@Path("/testget")
public class TestGetServlet extends MasterServlet {

    @Inject
    MyService service;

    @GET
    @Produces({"text/plain", MediaType.TEXT_PLAIN})
    public String testGet() {
        //service = Configuration.getServiceInjector().getInstance(MyService.class);
        return "get servlet functional";
    }
}

是否注意到testGet()方法中的注释行?如果我改为这样做并删除上面的@Inject批注,则一切正常,这表明Grizzly并未按照我期望的方式创建我的servlet。

我认为正在发生的事情是,灰熊对吉斯一无所知。一切似乎都表明Grizzly没有看到Configuration类,尽管事实是,通过将其放入我测试的@Before方法中,它似乎至少对使用它的类是 available (请参阅: TestGetServlet类中的注释行)。我只是不知道如何解决。

1 个答案:

答案 0 :(得分:2)

我仍在设法解决这个问题,但与此同时,我从Guice切换到HK2,这花了点功夫,但我认为这对将来遇到此问题的人可能会有帮助。

我认为这是一个答案,因为说实话,我尝试绕过Guice-HK2桥但仍将Guice与Jersey结合使用并不是最佳方法。

从Guice切换到HK2需要花点时间做,并且没有关于所有答案的全面指南。例如,依赖关系确实很挑剔。如果您尝试使用Jersey 2.27,则可能会遇到著名的

  

java.lang.IllegalStateException:找不到InjectionManagerFactory

错误。由于HK2本身,Jersey 2.27不能与以前的版本向后兼容。我仍在努力使所有功能正常工作,但与此同时,我必须将所有Jersey依赖项降级为2.26-b06才能使HK2正常工作。

庆幸的是,Jersey已经实现了一堆HK2样板文件,因此,要进行注入工作,您需要正确使用@ Contract,@ Service(有关详细信息,请参阅HK2文档),然后再使用两个新的类,如下所示:

public class MyHK2Binder extends AbstractBinder {
    @Override
    protected void configure() {
        // my service here is a singleton, yours might not be, so just omit the call to in()
        // also, the order here is switched from Guice! very subtle!
        bind(MyServiceImpl.class).to(MyService.class).in(Singleton.class);
    }
}

这:

public class MyResourceConfig extends ResourceConfig {

    public MyResourceConfig() {
        register(new MyHK2Binder());
        packages(true, "com.mycompany");
    }
}

足够简单,但这仅适用于应用程序本身。测试容器对此一无所知,因此您必须自己在测试类中重做Binder和ResourceConfig,如下所示:

public class TestServletTest extends JerseyTest {

    @Test
    public void testServletFunctional() {

        final String response = target("/testget").request().get(String.class);
        assertEquals("get servlet functional", response);
    }

    @Before
    public void setup() {
    }

    @Override
    protected Application configure() {
        return new TestServletBinder(TestGetServlet.class);
    }

    public class TestServletBinder extends ResourceConfig {
        public TestServletBinder(Class registeree) {
            super(registeree);
            register(new MyHK2Binder());
            packages(true, "com.mycompany");
        }
    }
}

执行此操作实际上很好,因为您可以将Binder换成测试绑定器,而在其中将服务绑定到模拟服务或其他东西。我在这里还没有做过,但这很容易做:用一个像这样进行绑定的寄存器替换对register()的调用中的新MyHK2Binder():

bind(MyTestServiceImpl.class).to(MyService.class).in(Singleton.class);

瞧。非常好。显然,使用命名绑定可以达到类似的结果,但是效果很好,甚至可能更简单,更清晰。

希望这可以帮助某人节省我花很多时间来解决这个问题。