【问题标题】:Injection of @PersistenceContext in CDI-Unit在 CDI 单元中注入 @PersistenceContext
【发布时间】:2016-01-19 17:44:35
【问题描述】:

这是单元测试代码。当我们运行单元测试代码(SampleServiceTest2)时; AbstractDao 中注入的 EntityManager 始终为空!我们如何在单元测试期间注入 em。

*** SampleServiceTest2.java

import javax.inject.Inject;

import org.jglue.cdiunit.CdiRunner;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(CdiRunner.class)
public class SampleServiceTest2 {

    @Inject SampleService greeter; 

    @Test
    public void testGreeter() throws Exception { 
        System.out.println("before2");
        greeter.addSampleData(new SampleDataDto(), new KullaniciDto()); 
        System.out.println("after2");
    } 
}

*** SampleService.java

import javax.ejb.Stateless;
import javax.inject.Inject;
....

@Stateless
@SecuredBean
public class SampleService {

    @Inject 
    SampleLogic sampleLogic;

    @Yetki(tag="perm_add_sample_data")
    public void addSampleData(SampleDataDto data, KullaniciDto aktifKullaniciDto){
        SampleDataHelper sampleDataHelper = new SampleDataHelper();

        SampleData sampleData = sampleDataHelper.getEntity(data);
        KullaniciHelper kullaniciHelper = new KullaniciHelper();

        Kullanici kullanici = kullaniciHelper.getEntity(aktifKullaniciDto);
        sampleLogic.addData(sampleData, kullanici);
    }

}

**** SampleLogic.java

import javax.inject.Inject;

....

public class SampleLogic {
    @Inject 
    SampleDataDao sampleDataDao;

    public void addData(SampleData data, Kullanici kullanici) {
        addData1(data,kullanici);   
        System.out.println("SampleLogic : addData() called!");
    }

    public void addData1(SampleData data, Kullanici kullanici) {
        sampleDataDao.create(data, kullanici);
    }
}

**** SampleDataDao.java

public class SampleDataDao extends AbstractDao<SampleData> {
    private static final long serialVersionUID = 1L;
}

**** AbstractDao.java

public abstract class AbstractDao<T extends BaseEntity> implements Serializable {

    private static final long serialVersionUID = 1L;

    @PersistenceContext(unitName="meopdb")
    private EntityManager em;

    protected EntityManager getEm() {
        return em;
    }

    @SuppressWarnings("rawtypes")
    private Class entityClass;


    @SuppressWarnings("rawtypes")
    private Class getEntityClass() {
        if (entityClass == null) {
            entityClass = (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        }
        return entityClass;
    }

    public T create(T t, Kullanici kullanici) {
        if (t.getId() != null) {
            throw new IllegalStateException("Create Operation: Oid should be null");
        }
        t.setId(getSeqNextValue(t));
        t.setEklemeZamani(new Timestamp(Calendar.getInstance().getTimeInMillis()));
        t.setEkleyenKullaniciId(kullanici.getId());

        t.setDurumId(EnumDurum.AKTIF.getValue());

        t = em.merge(t);
        em.flush();
        return t;
    }
}

【问题讨论】:

    标签: jpa jakarta-ee junit cdi cdi-unit


    【解决方案1】:

    如果您使用 CDIUnit 进行测试,您得到的唯一结果就是 CDI 注入,而不是 Java EE 的全部功能。使用@PersistenceContext 将 entityManager 注入AbstractDAO 不是独立 CDI 的一部分,仅当应用程序在 Java EE 应用程序服务器中运行时才受支持。

    解决方法是使用CDI机制注入EntityManager,创建producer。然后可以在单元测试中切换生产者以提供测试 entityManager。但是,在独立的单元测试中设置 JPA 并不是那么简单,因为您需要直接在 persistence.xml 文件中指定连接属性。此外,不要忘记将 JPA 实现(hibernate、eclipselink)的依赖项添加到您的测试依赖项中。

    但是,如果您不想调整应用程序的代码,或者您在测试中需要的不仅仅是 CDI,您应该查看Arquillian Java EE test framework

    以下是 CDIUnit 的示例:

    public abstract class AbstractDao<T extends BaseEntity> implements Serializable {
    ...
        @Inject
        @Named("meopdb")
        private EntityManager em;
    ...
    }
    
    // producer in application - just a wraper over `@PersisteneContext`
    public class EntityManagerProducer {
        @Produces
        @PersistenceContext(unitName="meopdb")
        @Named("meopdb")
        private EntityManager em;
    }
    
    /* producer in your test sources - it creates entityManager via API calls instead of injecting via `@PersistenceContext`. Also, a different persistence unit is used so that it does not clash with main persistence unit, which requires datasource from app server 
    */
    public TestEntityManagerProducer {
        @Produces
        @ProducesAlternative // CDIUnit annotation to turn this on as an alternative automatically
        @Named("meopdb")
        public EntityManager getEm() {
            return Persistence
                    .createEntityManagerFactory("meopdb-test")
                    .createEntityManager();
        }
    
    }
    

    这还不够。您需要使用名为“meopdb-test”的测试持久性单元在测试资源中创建一个新的persistence.xml。对于这个单元,您需要指定RESOURCE_LOCAL transaction-type,并指定连接信息。最后一件事不要忘记 - 您需要在 persistence.xml 或外部 orm 文件中列出所有实体。这是因为您的测试在应用程序服务器之外运行。在应用服务器内部,JPA 可以自动查找实体。

    【讨论】:

    • 如果您选择使用 arquillian 测试框架,Forge tool 带有 arquillian 插件应该可以帮助您从命令行或 IDE 设置测试。
    【解决方案2】:

    正如@OndroMih 所说,在 CDI-Unit 中,您唯一得到的就是 CDI 注入。所以你必须作弊。

    您可以使用扩展为所有@PersistenceContext 注入添加javax.inject.Inject 注释

    import java.util.Set;
    
    import javax.enterprise.event.Observes;
    import javax.enterprise.inject.spi.*;
    import javax.inject.Inject;
    import javax.persistence.PersistenceContext;
    
    import org.apache.deltaspike.core.util.metadata.AnnotationInstanceProvider;
    import org.apache.deltaspike.core.util.metadata.builder.AnnotatedTypeBuilder;
    
    
    
    public class AddInjectToPersistenceContextInjectionsCdiExtension implements Extension {
        <T> void processAnnotatedType(@Observes ProcessAnnotatedType<T> pat) {
           Set<AnnotatedField<? super T>> fields = pat.getAnnotatedType().getFields();
           for (AnnotatedField<? super T> field : fields) {
              if (shouldInjectionAnnotationBeAddedToField(field)) {
                 AnnotatedType<T> at = pat.getAnnotatedType();
                 AnnotatedTypeBuilder<T> builder = new AnnotatedTypeBuilder<T>().readFromType(at);
                 Inject injectAnnotation = AnnotationInstanceProvider.of(Inject.class);
                 builder.addToField(field, injectAnnotation);
                 pat.setAnnotatedType(builder.create());
              }
           }
        }
    
        private <X> boolean shouldInjectionAnnotationBeAddedToField(AnnotatedField<? super X> field) {
           return !field.isAnnotationPresent(Inject.class) && 
              field.isAnnotationPresent(PersistenceContext.class);
        }
    }
    

    并在测试类中生成合适的EntityManager

    @RunWith(CdiRunner.class)
    @AdditionalClasses(AddInjectToPersistenceContextInjectionsCdiExtension.class)
    public class SampleServiceTest2 {
    
        @Inject SampleService greeter;
    
        EntityManagerFactory emf;
    
        @PostConstruct
        void init() {
            emf = Persistence.createEntityManagerFactory("integration");
        }
    
        @Produces
        EntityManager createEntityManager() {
            return emf.createEntityManager();
        }
    
        @Test
        public void testGreeter() throws Exception  {
    
        }
    }
    

    它并不完全等同于 Java EE 容器的功能,但它通常足够接近。

    【讨论】:

      猜你喜欢
      • 2015-02-14
      • 1970-01-01
      • 2018-07-07
      • 2017-12-13
      • 2019-06-15
      • 1970-01-01
      • 2018-04-11
      • 2012-07-18
      • 2011-06-10
      相关资源
      最近更新 更多