繁体   English   中英

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

[英]Unit testing JAX-RS/Jersey servlet with Guice Injections

我有一个使用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.servlet.TestGetServlet.service(TestGetServlet.java:21)字段中的com.mycompany.MyService时

测试如下所示:

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

您会在设置方法中注意到我正在手动创建Configuration类,因为我不能依靠测试容器(Grizzly)来创建它(我没有这两行就得到了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不了解Guice。 一切似乎都表明Grizzly没有看到Configuration类,尽管事实是,将其放入我测试的@Before方法中似乎至少对使用它的类可用 (请参阅:TestGetServlet类中的注释行) )。 我只是不知道如何解决。

我仍在尝试解决此问题,但与此同时,我从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);

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM