在JAX-RS单元测试中挂钩@EJB或@PersistenceContext注入

Mat*_*ell 14 unit-testing ejb jax-rs jersey

我很高兴学习JAX-RS和Jersey,但我遇到了试图测试需要注入DAO的简单资源的障碍,如下所示:

@Stateless
@Path("simple")
public class SimpleResource {

    @PersistenceContext
    private EntityManager em;

// @GET, etc...

}
Run Code Online (Sandbox Code Playgroud)

(我将转向更抽象的DAO模式,但问题是一样的,即如何注入@EJB DAO?)

在我的单元测试中,我使用嵌入式Jetty服务器在其web.xml中配置Jersey,我想挂钩资源的生命周期,以便我可以注入一个模拟EntityManager,但我没有找到一个干净的答案经过大量的搜索.你能帮我吗?我遇到的一些可能的方向:

1)在我的代码中使用JNDI上下文查找来获取DAO bean,并在测试中注册模拟对象.

而不是@EJB或@PersistenceContext,在资源的构造函数中使用这样的东西:

  theDAO = (DAOImpl) new InitialContext().lookup("java:global/EJB/DAOImpl");
Run Code Online (Sandbox Code Playgroud)

但是,这意味着我的测试环境需要支持JNDI,而在Jetty中这样做可能会带来一些痛苦.另外,它不使用干净的注释方法.

2)使用方法注射.

注入方法,以便我可以设置DAO后实例化,例如,

@PersistenceContext(name = "persistence/pu00")
public void setPersistenceUnit00(final EntityManager em) {
    em00 = em;
}
Run Code Online (Sandbox Code Playgroud)

要么

private MyEjbInterface myEjb;
@EJB(mappedName="ejb/MyEjb")
public void setMyEjb(MyEjb myEjb) {
    this.myEjb = myEjb;
}
Run Code Online (Sandbox Code Playgroud)

但是,要做到这一点,我需要Jersey实例化的实例,例如,SimpleResource.我怎么做到的?

3)使用反射.

一种DIY注射,如:

public static void setPrivateField(Class<? extends Object> instanceFieldClass, Object instance, String fieldName, Object fieldValue) {
    Field setId = instanceFieldClass.getDeclaredField(fieldName);
    setId.setAccessible(true);
    setId.set(instance, fieldValue);
}
Run Code Online (Sandbox Code Playgroud)

同样,我需要Jersey实例化的实例.

4)使用注射提供者.

我仍然粗略地说明它是如何工作的,但看起来Jersey提供了一种定义可定制注射注释的方法,例如,

    @Provider
    public class EJBProvider implements InjectableProvider<EJB, Type> {

    public ComponentScope getScope() {
    return ComponentScope.Singleton;
    }

    public Injectable getInjectable(ComponentContext cc, EJB ejb, Type t) {
    if (!(t instanceof Class)) {
        return null;
    }
    try {
        Class c = (Class) t;
        Context ic = new InitialContext();
        final Object o = ic.lookup(c.getName());
        return new Injectable<Object>() {
        public Object getValue() {
            return o;
        }
        };
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
    }
}
Run Code Online (Sandbox Code Playgroud)

使用辅助类的变体:

Server server = new Server(8080);
Context root = new Context(server,"/",Context.SESSIONS);

ResourceConfig rc = new PackagesResourceConfig("edu.mit.senseable.livesingapore.platform.restws.representations");
rc.getSingletons().add(new SingletonTypeInjectableProvider<javax.ws.rs.core.Context, Myobj>(Myobj.class, new Myobj(12,13)){});

root.addServlet(new ServletHolder(new ServletContainer(rc)), "/");
server.start();
Run Code Online (Sandbox Code Playgroud)

有了这个用途:

@Path("/helloworld")
public class HelloWorldResource {
    @Context Myobj myClass;
    ....
}
Run Code Online (Sandbox Code Playgroud)

这对@EJB或@PersistenceContext是否可行?

5)扩展javax.ws.rs.core.Application.

粗略对此,但是:

@javax.ws.rs.ApplicationPath("application")
public class InjectionApplication extends javax.ws.rs.core.Application {

  private Set<Object> singletons = new HashSet<Object>();
  private Set<Class<?>> classes = new HashSet<Class<?>>();

  public InjectionApplication() {
    // no instance is created, just class is listed
    classes.add(BookResource.class);
  }

  @Override
  public Set<Class<?>> getClasses() {
    return classes;
  }

  @Override
  public Set<Object> getSingletons() {
    return singletons;
  }
}
Run Code Online (Sandbox Code Playgroud)

6)扩展ServletContainer.

使用InjectableProvider的旧式?看起来更复杂:

public class ServletAdapter extends ServletContainer {

@Override
protected void configure(ServletConfig servletConfig, ResourceConfig rc, WebApplication wa) {
    super.configure(servletConfig, rc, wa);

    rc.getSingletons().add(new InjectableProvider<Resource, Type>() {

        public ComponentScope getScope() {
            return ComponentScope.Singleton;
        }

        public Injectable<Object> getInjectable(ComponentContext ic, Resource r, Type c) {
            final Holder value = new Holder();

                Context ctx = new InitialContext();
                try {
                    value.value = ctx.lookup(r.name());
                } catch (NamingException ex) {

                    value.value = ctx.lookup("java:comp/env/" + r.name());
                }

            return new Injectable<Object>() {
                public Object getValue() {
                    return value.value;
                }
            };
        }
    });
}
}  
Run Code Online (Sandbox Code Playgroud)

7)使用嵌入式EJB容器.

例如,http://openejb.apache.org.这非常沉重,我预计工作会变得很混乱.(事实上​​,让我沿着"Jetty + Jersey"路线开始的是GlassFish Embedded中围绕安全登录的一个错误.我还查看了其他Java EE 6应用程序容器,如JBoss AS,但每个都有嵌入式模式的问题,用户有限社区支持.)

8)使用Spring或Guice等第三方IoC库.

Spring显然常用于解决这些问题(在单元测试时注入模拟),但我想避免学习另一大套API - 纯Java EE已经足够挑战了!但如果这是最好的解决方案我就是游戏.我还没有仔细看过Spring或Guice.

你成功使用过这些吗?您喜欢的其他任何解决方案 我真的很期待你的建议.提前谢谢 - 亚光

rdc*_*rng 0

如果您只需要一个EntityManager内置的 Jetty 容器,为什么首先要使用注入呢?您可以将 JPA 实现之一(例如 eclipselink 或 hibernate)放在类路径上,配置资源本地持久性单元,然后按如下方式获取它:

EntityManagerFactory emf = Persistence.createEntityManagerFactory("your unit name");
EntityManager em = emf.createEntityManager();
Run Code Online (Sandbox Code Playgroud)

@EJB为了测试 JAX-RS 类,拥有一些行为类似于您的东西(也许是静态 DAO 工厂?)就足够了。

如果您确实希望单元测试尽可能接近 Java EE 环境,请考虑使用 Arquillian (http://www.jboss.org/arquillian.html) 运行它们。它直接在 Java EE 容器上运行测试 - 这很简单,并且有很棒的文档。