很长时间以来,我听到很多人对Arquillian说好话 。 虽然我一直在阅读有关其用法的文章,但实际上我无法在一篇文章中找到涵盖我认为重要的某些方面的文章。 当然,我看起来还不够努力。
我要讲的要点是:
- 使用JPA。 我只是在这里使用EclipseLink,
- 使用内存数据库
- 使用CDI注射,
- EJB的使用,例如本地无状态会话Bean,
- 使用JSR-303 Bean验证,
- 使用(嵌入式)玻璃鱼进行集成测试。
我花了一些时间来收集信息以启动并运行该项目。 我以为我专门写这篇文章来帮助那些有类似要求的人。 那么,我们还在等什么! 开始吧!!!
配置pom.xml
当然,我们需要配置项目以使用Arquillian,还需要使用EclipseLink作为持久性提供程序。 但是,请记住,我们还要在决定使用内存数据库之前就做出决定。 您可能希望在此pom.xml中包括您的依赖项。 但是,我决定使用Derby,它在标准Oracle Java SDK类路径中可用。
无论如何,所以pom.xml看起来像这样:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>inout</artifactId>
<groupId>id.co.dwuysan</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>id.co.dwuysan</groupId>
<artifactId>inout-ejb</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>ejb</packaging>
<name>inout-ejb</name>
<properties>
<endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<netbeans.hint.deploy.server>gfv3ee6</netbeans.hint.deploy.server>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.jboss.arquillian</groupId>
<artifactId>arquillian-bom</artifactId>
<version>1.0.0.Final</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.3.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>javax.persistence</artifactId>
<version>2.0.3</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.jpa.modelgen.processor</artifactId>
<version>2.3.2</version>
<scope>provided</scope>
</dependency>
<!-- test -->
<dependency>
<groupId>org.jboss.arquillian</groupId>
<artifactId>arquillian-bom</artifactId>
<version>1.0.3.Final</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.glassfish.main.extras</groupId>
<artifactId>glassfish-embedded-all</artifactId>
<version>3.1.2.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.container</groupId>
<artifactId>arquillian-glassfish-embedded-3.1</artifactId>
<version>1.0.0.CR3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.junit</groupId>
<artifactId>arquillian-junit-container</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- environment requirement -->
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12.4</version>
<configuration>
<argLine>-XX:-UseSplitVerifier</argLine>
<systempropertyvariables>
<java.util.logging.config.file>
${basedir}/src/test/resources/logging.properties
</java.util.logging.config.file>
</systempropertyvariables>
<systemProperties>
<property>
<name>derby.stream.error.file</name>
<value>target/derby.log</value>
</property>
</systemProperties>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<compilerArguments>
<endorseddirs>${endorsed.dir}</endorseddirs>
</compilerArguments>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-ejb-plugin</artifactId>
<version>2.3</version>
<configuration>
<ejbVersion>3.1</ejbVersion>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.1</version>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<outputDirectory>${endorsed.dir}</outputDirectory>
<silent>true</silent>
<artifactItems>
<artifactItem>
<groupId>javax</groupId>
<artifactId>javaee-endorsed-api</artifactId>
<version>6.0</version>
<type>jar</type>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>java.net</id>
<url>http://download.java.net/maven/</url>
</repository>
<repository>
<id>JBOSS_NEXUS</id>
<url>http://repository.jboss.org/nexus/content/groups/public</url>
</repository>
<repository>
<url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url>
<id>eclipselink</id>
<layout>default</layout>
<name>Repository for library EclipseLink (JPA 2.0)</name>
</repository>
</repositories>
</project>
如上面的XML所示,我们所做的事情是:
- 包括使用Arquillian,使用嵌入式Glassfish
- 包括使用EclipseLink
- 设置Derby道具以备后用,即记录和创建数据库的位置。
创建“案例”,即我们的JPA,EJB,CDI
当然,我们首先要创建一个案例,以便以后进行测试。 我假设您熟悉JPA,EJB,CDI。 因此,下面是使用这些技术的类的快速概览。
JPA类,Outlet.java
package id.co.dwuysan.inout.entity;
// imports omitted
@Entity
@NamedQueries({
@NamedQuery(
name = Outlet.FIND_BY_NAME,
query = "SELECT o FROM Outlet o WHERE o.name = :name")
})
public class Outlet implements Serializable {
public static final String FIND_BY_NAME = "Outlet#FIND_BY_NAME";
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "code", length = 50, insertable = true,
updatable = false, unique = true)
@Size(message = "{dwuysan.nameSizeError}", min = 1, max = 50)
@NotNull
private String name;
/* Accessors and mutators goes here */
@Override
public int hashCode() {
// omitted
}
@Override
public boolean equals(Object obj) {
// omitted
}
}
Persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="inoutPU" transaction-type="JTA">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<jta-data-source>inoutDb</jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
</properties>
</persistence-unit>
</persistence>
然后,我们添加一个生产者方法来提供我们的PersistenceContext以及使用它的EJB。
EntityManagerProducer.java
package id.co.dwuysan.inout.util;
import javax.enterprise.inject.Produces;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
public class EntityManagerProducer {
@Produces
@PersistenceContext
private EntityManager em;
}
OutletService.java
package id.co.dwuysan.inout.service;
// imports omitted
@Stateless
@LocalBean
public class OutletService {
@Inject
private EntityManager em;
@Resource
private Validator validator;
public Outlet createOutlet(final String name) {
final Outlet outlet = new Outlet();
outlet.setName(name);
final Set<ConstraintViolation<Outlet>> violations = this.validator.validate(outlet);
if (!violations.isEmpty()) { throw new ConstraintViolationException(new HashSet<ConstraintViolation<?>>(violations)); }
return this.em.merge(outlet);
}
public Outlet getOutlet(final String name) {
final Query query = this.em.createNamedQuery(Outlet.FIND_BY_NAME);
query.setParameter("name", name);
try {
return (Outlet) query.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
}
设置beans.xml和ValidationMessages.properties
不要忘记:
- 在src / main / resources / META-INF下添加
beans.xml
,并 - 在src / main / resources下添加
ValidationMessages.properties
,并 -
dwuysan.nameSizeError= error message you like here
配置您的消息dwuysan.nameSizeError= error message you like here
- 在src / main / resources / META-INF下添加
配置用于测试目的
在这一点上,如果您部署,它应该可以工作。 但是,这不是我们的目标。 我们希望使用嵌入式Glassfish使它在Arquillian下工作。
首先,让我们使用Derby数据库准备嵌入式玻璃鱼的配置。 该文件是glassfish-resources.xml
。 就我而言,我只是将该文件放在一个新目录下,主要用于分离,即src/test/resources-glassfish-embedded/glassfish-resources.xml
。
glassfish-resources.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC
"-//GlassFish.org//DTD GlassFish Application Server 3.1 Resource Definitions//EN"
"http://glassfish.org/dtds/glassfish-resources_1_5.dtd">
<resources>
<jdbc-resource pool-name="ArquillianEmbeddedDerbyPool"
jndi-name="jdbc/arquillian"/>
<jdbc-connection-pool name="ArquillianEmbeddedDerbyPool"
res-type="javax.sql.DataSource"
datasource-classname="org.apache.derby.jdbc.EmbeddedDataSource"
is-isolation-level-guaranteed="false">
<property name="databaseName" value="target/databases/derby"/>
<property name="createDatabase" value="create"/>
</jdbc-connection-pool>
</resources>
这是不言自明的。 只需记住将数据库配置为在target/databases/derby
上创建,以便在执行mvn clean
时将对其进行清理。
下一步是配置Arquillian以“识别”此glassfish-resources.xml
。 为此,请在src/test/resources
目录下添加arquillian.xml
。
glassfish-resources.xml
<?xml version="1.0" encoding="UTF-8"?>
<arquillian xmlns="http://jboss.org/schema/arquillian"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jboss.org/schema/arquillian http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
<engine>
<property name="deploymentExportPath">target/arquillian</property>
</engine>
<container default="true" qualifier="glassfish">
<configuration>
<property name="resourcesXml">src/test/resources-glassfish-embedded/glassfish-resources.xml</property>
</configuration>
</container>
</arquillian>
下一步是准备我们的persistence.xml
。 我们已经有一个了,但是请记住我们需要提供一个在内存中的,并利用我们的嵌入式Glassfish提供的jdbc连接(请参阅上面的glassfish-resources.xml
,该文件在以下目录中提供了jdbc-resource-pool
。 JNDI名称为jdbc/arquillian
,在我的情况下,我将此名称为test-persistence.xml
命名为src/test/resources
test-persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="inoutPU" transaction-type="JTA">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<jta-data-source>jdbc/arquillian</jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<shared-cache-mode>ALL</shared-cache-mode>
<properties>
<property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.EmbeddedDriver" />
<property name="javax.persistence.jdbc.url" value="jdbc:derby:target/databases/derby;create=true" />
<property name="eclipselink.ddl-generation" value="drop-and-create-tables" />
<property name="eclipselink.target-database" value="Derby"/>
<property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
<property name="eclipselink.debug" value="OFF"/>
<property name="eclipselink.weaving" value="static"/>
<!--<property name="eclipselink.logging.level" value="FINEST"/>-->
<property name="eclipselink.logging.level.sql" value="FINE"/>
<property name="eclipselink.logging.parameters" value="true"/>
<!--<property name="eclipselink.logging.level.cache" value="FINEST"/>-->
<property name="eclipselink.logging.logger" value="DefaultLogger"/>
</properties>
</persistence-unit>
</persistence>
一切准备就绪后,我们现在就可以与Arquillian一起编写单元测试了。 在这种情况下,最好测试我们的服务EJB,因为这是我们将使用JPA,CDI和Validation的地方。
OutletServiceTest.java
package id.co.dwuysan.inout.service;
// imports omitted
@RunWith(Arquillian.class)
public class OutletServiceTest {
@Inject
private OutletService outletService;
@Deployment
public static JavaArchive createTestArchive() {
return ShrinkWrap.create(JavaArchive.class)
.addClass(Outlet.class)
.addClass(OutletService.class)
.addClass(EntityManagerProducer.class)
.addAsManifestResource("test-persistence.xml",
ArchivePaths.create("persistence.xml"))
.addAsManifestResource("META-INF/beans.xml",
ArchivePaths.create("beans.xml"))
.addAsResource("ValidationMessages.properties");
}
@Test
public void testCreateOutlet() throws Exception {
final String outletName = "Outlet 001";
final Outlet outlet = this.outletService.createOutlet(outletName);
Assert.assertNotNull(outlet);
// check retrieval
final Outlet retrievedOutlet = this.outletService.getOutlet(outletName);
Assert.assertEquals(outlet.getName(), retrievedOutlet.getName());
}
@Test(expected = ConstraintViolationException.class)
public void testCreateOutletWithEmptyName() throws Exception {
try {
final Outlet outlet = this.outletService.createOutlet("");
} catch (EJBException e) {
final ConstraintViolationException cve = (ConstraintViolationException) e.getCause();
Assert.assertEquals("Total error message should only be one",
1, cve.getConstraintViolations().size());
Assert.assertEquals("Message must be correct",
"Name must be provided",
cve.getConstraintViolations().iterator().next().getMessage());
throw cve;
}
}
}
在上面的示例中,第一个测试是测试成功的案例。 给定名称,检索将导致提供与参数相同名称的Outlet
实体返回。 但是,在表面之下,如果我们回顾OutletService.java
的主体,我们实际上正在测试:
- 持久性(JPA),进入基础Derby
- EJB注入此测试/ li>
- 通过
Producer
方法(CDI)注入的PersistenceContext
- 测试没有违反验证
- 测试我们的
NamedQuery
第二项测试旨在测试消息是否正确内插。 参考前面提到的内容,对于我的错误消息,我将以下条目放入ValidationMessages.properties
:
dwuysan.nameSizeError=Name must be provided
因此,我们需要测试“ Outlet
Bean验证”消息是否正确内插。
请注意第二项测试。 注意,首先,我们正在捕获EJBException
。 那是因为在EJB中抛出的任何运行时异常都将被包装到EJBException
,因此需要通过#getCause()
提取它。
所以,你去了。 现在,您可以添加更多服务并开始Arquillian测试。 快乐编码
未来调查
当然,许多Java EE应用程序都需要认证和授权,这通常是通过JAAS完成的。 例如,使用上面的简单示例,假设要修改服务以检索当前用户可以访问的出口,那么我们当然需要获取当前用户的身份。 通常,这是通过EJBContext.getCallerPrincipal()
. I wonder how we can do this using Arquillian and embedded Glassfish.
EJBContext.getCallerPrincipal()
. I wonder how we can do this using Arquillian and embedded Glassfish.