Integration Testing in Java EE, Adam Bean

Original author: Adam Bien
  • Transfer
  • Tutorial
Pragmatic integration testing can increase your productivity and guarantee the deployment of a JavaEE application.

Adam Bean (Germany) is a consultant, trainer, architect, member of the Java EE 6 and 7 expert group, EJB 3.X, JAX-RS, and JPA 2.X JSRs. Java Champion, Top Java Ambassador 2012 and JavaOne Rock Star 2009, 2011, 2012 and 2013 He is the author of Real World Java EE Patterns – Rethinking Best Practices and Real World Java EE Night Hacks – Dissecting the Business Tier.

We suggest that you familiarize yourself with the translation of Adam’s article “ Integration Testing for Java EE
.

Introduction


In a previous article, “ Unit Testing for Java EE, ” I reviewed the approaches to unit testing Java applications and applications written using Java EE 6 simulating all external dependencies using the Mokito library. Unit tests are important for validating business logic, but do not guarantee the deployment of your Java EE 6 application.
Note: On the Java.Net resource you will find the Maven 3 project for this article (TestingEJBAndCDI) that has been tested using NetBeans 7 and GlassFish v3.x .

Note: On the Java.Net resource, you will find the Maven 3 project for this article ( TestingEJBAndCDI ), which was tested using NetBeans 7 and GlassFish v3.x.

Using different approaches in testing


Unit tests are fast and fine-grained. Integration tests are slow and "coarse-grained." Instead of using the arbitrary division of unit and integration tests into fast and slow, respectively, to improve performance, we will take into account their specifics. Fine-grained unit tests should be performed quickly. Typically, tests are written for small pieces of functionality before they are integrated into a larger subsystem. Unit tests are incredibly fast - hundreds of tests can be run in milliseconds. Using unit tests, it is possible to conduct fast iterations and not wait for integration tests to complete.
Integration tests are performed after successfully passing unit tests. Unit tests often fail, so integration tests are run less frequently. Due to the strict division into unit and integration tests, you can save several minutes (or even hours) on each test cycle.

Performance Testing


Pragmatic integration testing will really increase your productivity. Another thing is testing Java Persistence API (JPA) mappings and queries. Uploading the entire application to the server only to verify the correct syntax of the mappings and queries takes too much time.
JPA can be used directly from the unit test. Then the cost of restarting will be negligible. To do this, you just need to get an instance of EntityManager from EntityManagerFactory. For example, to test the mapping of the Prediction class, the EntityManager is embedded in the PredictionAudit class (see Example 1).

public class PredictionAuditIT {
 	private PredictionAudit cut;
 	private EntityTransaction transaction;
 @Before
 public void initializeDependencies(){
 	cut = new PredictionAudit();
 	cut.em = Persistence.createEntityManagerFactory("integration").
		createEntityManager();
 	this.transaction = cut.em.getTransaction();
 }
 @Test
 public void savingSuccessfulPrediction(){
 	final Result expectedResult = Result.BRIGHT;
	Prediction expected = new Prediction(expectedResult, true);
 	transaction.begin();
 	this.cut.onSuccessfulPrediction(expectedResult);
 	transaction.commit();
 	List allPredictions = this.cut.allPredictions();
 	assertNotNull(allPredictions);
assertThat(allPredictions.size(),is(1));
 }
 @Test
 public void savingRolledBackPrediction(){
 	final Result expectedResult = Result.BRIGHT;
 	Prediction expected = new Prediction(expectedResult, false);
 	this.cut.onFailedPrediction(expectedResult);
 }
}

Example 1. Embedding the EntityManager in the PredictionAudit class

Since in Example 1 the EntityManager runs outside the container, transactions can only be controlled by unit tests. Declarative transactions are not available in this case. This makes testing even easier, since the transaction boundary can be explicitly set inside the testing method. You can easily clear the EntityManager cache by calling the EntityTransaction # commit () method. Immediately after clearing the cache, the data becomes available in the database and can be verified during testing (see savingSuccessfulPrediction () in Example 1).

Standalone JPA Configurations



EntityManager is part of the JPA specification, has also been included in glassfish-embedded-all-dependency. The same dependency runs in EclipseLink. You only need an external database to store data. The Derby database does not require installation, can be launched in server mode or in embedded database mode and can store data in random access memory (in-memory) or on disk.

dependency>
 		org.apache.derbyderbyclient10.7.1.1test

Example 2. “Installation” of the Derby database Derby is

supported by the standard Maven repository and can be added to the project with one dependency (see example 2). In this case, the dependency is defined for the test scope because the JDBC driver is needed only during testing and should not be deployed or installed on the server.
When conducting unit testing without a container, it is impossible to use the Java Transaction (JTA) and javax.sql.DataSource functionality .

com.abien.testing.oracle.entity.Predictiontrue

Example 3. A persistence.xml file configured for unit testing

An additional persistence.xml file is created in the src / test / java / META-INF packageand is used solely for testing. Since there is no deployment process, all entities have to be described explicitly. Also, the transaction type is set to RESOURCE_LOCAL, which allows you to process transactions manually. Instead of declaring the data source, EntityManager accesses the database directly through the configured JDBC driver. The embedded Derby database is best suited for unit testing. EmbeddedDriver supports two options for configuring URLs: with data stored in a file or in memory. To test the JPA mapping and queries, a connection string with storage in memory (in-memory, see example 3) is used. All tables are created on the fly in RAM before the start of the next test and deleted after the test. Since there is no need to delete your data after testing,

More complex JPA tests require a certain set of test data, and using the in-memory configuration for such cases is inconvenient. Instead of RAM, the Derby database can use files to store and load data. To do this, just change the connection string:

org.apache.maven.pluginsmaven-failsafe-plugin2.7.1

Example 4. Configuring the FailSafe plug-in

Unit tests are performed during the standard execution of mvn clean install, and they can also be explicitly launched using the mvn surefire: test command. Integration tests can also be added to Maven phases using the execution tag:
org.apache.maven.pluginsmaven-failsafe-plugin2.7.1integration-testintegration-testintegration-testverifyverifyverify

Example 5. Registering the Failsafe plugin

When using the execution tag, the Failsafe plugin starts automatically executed with the mvn install , mvn verify or mvn integration-test commands . Unit tests are performed first, and then integration tests.
A strict division into integration and unit tests significantly speeds up the testing cycle. Thus, unit tests test the functionality of a method in a simulated environment, and they are several orders of magnitude faster than integration tests. An instant result is obtained after performing unit tests.
Only successful completion of all unit tests triggers initially slower integration tests. It is also possible to configure Continuous Integration separately for unit and integration testing. The mvn clean install command launches the mvn integration-tests command . Separation of processes gives more flexibility; You can restart tasks separately and receive notifications about the process of each task.

Killing Use Case for Embedded Integration Tests


Most integration tests can be performed without starting the container. JPA functionality can be tested using an EntityManager created locally. Business logic can also be conveniently and effectively tested outside the container. For you need to create mock objects for all dependent classes, services and everything else.
Let's assume that we need to bring out two error messages and keep them in one place for easy support. Instances of the String class can be implemented and used for configuration (see Example 6).

@Inject
	String javaIsDeadError;
	@Inject
	String noConsultantError;
		  //…
		if(JAVA_IS_DEAD.equals(prediction)){
 			throw new IllegalStateException(this.javaIsDeadError);
 		}
		  //…
		if(company.isUnsatisfied()){
			throw new IllegalStateException(this.noConsultantError);

Example 6. Throwing exceptions using embedded strings

The MessageProvider class supports configuration and returns a string using property values ​​(see Example 7).

@Singleton
	public class MessageProvider {
			public static final String NO_CONSULTANT_ERROR = "No consultant to ask!";
			public static final String JAVA_IS_DEAD_MESSAGE = "Please perform a sanity / reality check";
 		private Map defaults;
 		@PostConstruct
 		public void populateDefaults(){
 			this.defaults = new HashMap(){{
 				put("javaIsDeadError", JAVA_IS_DEAD_MESSAGE);
 				put("noConsultantError", NO_CONSULTANT_ERROR);
 			}};
 		}
 		@Produces
 		public String getString(InjectionPoint ip){
 			String key = ip.getMember().getName();
 			return defaults.get(key);
 		}
	}

Example 7. General configurator

If you look at the MessageProvider class (example 7), you can see that there is nothing left to test. Unless you can create a mock for the InjectionPoint parameter of the Map defaults field to test the search. The only non-tested part after dependency injection is the dependency injection itself and its use in the getString () method . Obtaining the field name, search, and implementation itself can only be fully tested inside the container. The purpose of the implementation is the OracleResource class , which uses the embedded values ​​to throw exceptions. (see article "Unit Testing for Java EE . ") In order to test this mechanism, you will either have to inject the embedded values ​​or extract the message from the exceptions. With this approach, you will not be able to test the" sharp points ", for example, unconfigured fields. A helper class created exclusively for the purpose testing, will give more flexibility and greatly simplify testing.

public class Configurable {
	@Inject
	private String shouldNotExist;
	@Inject
	private String javaIsDeadError;
	public String getShouldNotExist() {
		return shouldNotExist;
 	}
 	public String getJavaIsDeadError() {
 		return javaIsDeadError;
 	}
}

Example 8. A helper class for integration testing

The Configurable class (see example 8) is located in the src / test / java folder and was specially designed to simplify integration testing for the MessageProvider class . It is assumed that the javaIsDeadError field will be implemented, and the shouldNotExist field does not need to be configured, and during testing it will be null .

Aliens can help you



Arquillian – это интересное существо неземного происхождения (см. фильм «Люди в черном»), но в то же время это еще и фреймворк с открытым кодом, интегрированный в класс TestRunner фремворка JUnit. С его помощью у вас появляется возможность полного контроля над тем, какие классы развертываются и внедряются. Arquillian выполняет JUnit-тесты и имеет полный доступ к содержимому папки src/test/java. Специальные классы для тестирования, такие как Configurable (см. пример 8), можно использовать для упрощения тестирования, при этом тесты не попадут в папку с основным кодом приложения src/main/java, и нет необходимости включать их в поставку.

Arquillian состоит из двух частей: управляющей части и контейнера. Управляющая часть arquillian-junit runs the tests and implements a dependency injection mechanism inside the test case.

org.jboss.arquillianarquillian-junit1.0.0.Alpha5testorg.jboss.arquillian.containerarquillian-glassfish-embedded-3.11.0.0.Alpha5test

Example 9. Arquillian configuration for Maven 3

The dependency arquillian-glassfish-embedded-3.1 integrates with the application server. You can use both version 3.1 and 3.0.

Instead of glassfish, you can use the built-in JBoss server using the arquillian-jbossas-embedded-6 artifact , or the Tomcat and Weld servers.

import org.jboss.shrinkwrap.api.*;
 import javax.inject.Inject;
 import org.jboss.arquillian.junit.Arquillian;
 import org.junit.*;
 import static org.junit.Assert.*;
 import static org.hamcrest.CoreMatchers.*;
 @RunWith(Arquillian.class)
 public class MessageProviderIT {
 	@Inject
 	MessageProvider messageProvider;
 	@Inject
 	Configurable configurable;
 	@Deployment
 	public static JavaArchive createArchiveAndDeploy() {
 		return ShrinkWrap.create(JavaArchive.class, "configuration.jar").
 			addClasses(MessageProvider.class, Configurable.class).
 			addAsManifestResource(
 			new ByteArrayAsset("".getBytes()),
 			ArchivePaths.create("beans.xml"));
 }
 @Test
 public void injectionWithExistingConfiguration() {
 	String expected = MessageProvider.JAVA_IS_DEAD_MESSAGE;
 	String actual = configurable.getJavaIsDeadError();
 	assertNotNull(actual);
 	assertThat(actual,is(expected));
 }
 @Test
 public void injectionWithMissingConfiguration(){
 	String shouldNotExist = configurable.getShouldNotExist();
 	assertNull(shouldNotExist);
 }

Example 10. Integration tests using Arquillian

After configuring the dependencies, you can use Arquillian as a test management system. Unit tests are performed transparently in Arquillian. Maven, Ant or even your IDE will simply use Arquillian instead of the "native" test execution system. Although this redirection is transparent, it allows Arquillian to embed deployed Contexts and Dependency Injection (CDI) or Enterprise JavaBeans (EJB) components or other Java EE resources directly into your tests. Arquillian is just a thin layer over the implementation of the application server. It covertly downloads GlassFish, JBoss, or Tomcat and runs tests on the "native" embedded application server. For example, it can use Embedded GlassFish.

To start using Arquillian, we need to deploy the JAR archive. In the createArchiveAndDeploy method (see Example 10), a JAR is created and deployed with the MessageProvider and Configurable classes and with an empty beans.xml file. Deployed classes can be directly embedded in the unit test itself, which greatly simplifies testing. The injectionWithExistingConfiguration method accesses the Configurable class , which returns the values javaIsDeadError and shouldNotExist (see Example 8). You can test the code as if it was running inside the application server. On the one hand, this is just an illusion, on the other hand, no, because the tests actually launch the container.

Testing the impossible



It is difficult to predict what happens if the Instance Consultant> company dependency is not satisfied. This cannot happen in actual use, since at that time enough implementations of the Consultant class are always deployed . This situation makes testing in an integration environment impossible. But Arquillian makes it easy to test unmet and undefined dependencies by managing the deployment module.

@RunWith(Arquillian.class)
public class OracleResourceIT {
	@Inject
	OracleResource cut;
 	@Deployment
 	public static JavaArchive createArchiveAndDeploy() {
 		return ShrinkWrap.create(JavaArchive.class, "oracle.jar").
 			addClasses(OracleResource.class,MessageProvider.class, Consultant.class).
 			addAsManifestResource(
 			new ByteArrayAsset("".getBytes()),
 			ArchivePaths.create("beans.xml"));
 	}
 	@Test(expected=IllegalStateException.class)
 	public void predictFutureWithoutConsultants() throws Exception{
 		try {
 			cut.predictFutureOfJava();
 		} catch (EJBException e) {
 			throw e.getCausedByException();
 		}
	}
}

Example 11. Testing is impossible

only the necessary classes OracleResource , MessageProvider and Consultant archived oracle.jar method createArchiveAndDeploy of Example 11. While class OracleResource also uses the Event , to send the Result (see. Example 12), we ignore the event PredictionAudit not deploim his.

@Path("javafuture")
@Stateless
public class OracleResource {
 	@Inject
 	Instance company;
 	@Inject
 	Event eventListener;
 	@Inject
 	private String javaIsDeadError;
 	@Inject
 	private String noConsultantError;
// бизнес-логика опущена
}

Example 12. Required dependencies of the OracleResource class.

We assume that the Event will be swallowed. The predictFutureWithoutConsultants method calls the OracleResource # predictFutureOfJava method and expects to throw an IllegalStateException after checking the condition in checkConsultantAvailability (see Example 13).

public String predictFutureOfJava(){
		checkConsultantAvailability();
		Consultant consultant = getConsultant();
		Result prediction = consultant.predictFutureOfJava();
		eventListener.fire(prediction);
		if(JAVA_IS_DEAD.equals(prediction)){
 			throw new IllegalStateException(this.javaIsDeadError);
 		}
 		return prediction.name();
	}
	void checkConsultantAvailability(){
		if(company.isUnsatisfied()){
			throw new IllegalStateException(this.noConsultantError);
		}
	}

Example 13. A precondition check in the OracleResource class

Note that a javax.ejb.EJBException is thrown instead of the expected IllegalStateException (see Example 14).

WARNING: A system exception occurred during a call on the EJB side.

class OracleResource 
public java.lang.String com.abien.testing.oracle.boundary.OracleResource.predictFutureOfJava()
javax.ejb.EJBException
//опускаем несколько строк класса stacktrace…
	at $Proxy126.predictFutureOfJava(Unknown Source)
	at com.abien.testing.oracle.boundary.__EJB31_Generated__OracleResource__                    __Intf____Bean__.predictFutureOfJava(Unknown Source)

Example 14. Implementing the Stack Trace class using the EJB Proxy.

The unit test accesses the actual EJB 3.1 component through the public interface. IllegalStateException is an unchecked exception that causes the current transaction to be rolled back, and its action wraps in the javax.ejb.EJBException exception . You have to get an IllegalStateException from an EJBException and throw it again.

Testing Rollback



Transaction rollback behavior can be easily tested by introducing a helper class: TransactionRollbackValidator . This is a regular EJB 3.1 component with access to SessionContext .

@Stateless
public class TransactionRollbackValidator {
	@Resource
	SessionContext sc;
	@EJB 
	OracleResource os;
	public boolean isRollback(){
		try {
			os.predictFutureOfJava();
		} catch (Exception e) {
 			//swallow all exceptions intentionally
			//намеренно проглатываем все исключения
 		}
 		return sc.getRollbackOnly();
	}
}

Example 15. 3.1 EJB component for testing

Test class TransactionRollbackValidator causes class OracleResource , handles all exceptions and returns the current status of a transaction rollback ( rollback ). You just have to slightly expand the deployment and testing section of the OracleResourceIT class to verify that the transaction was rolled back successfully (see Example 16).

@RunWith(Arquillian.class)
public class OracleResourceIT {
// другие исключения опущены
	@Inject
	TransactionRollbackValidator validator;
	@Deployment
	public static JavaArchive createArchiveAndDeploy() {
		return ShrinkWrap.create(JavaArchive.class, "oracle.jar").
			addClasses(TransactionRollbackValidator.class,
				OracleResource.class,MessageProvider.class, Consultant.class).
 			addAsManifestResource(
 			new ByteArrayAsset("".getBytes()),
 			ArchivePaths.create("beans.xml"));
	}
	@Test
	public void rollbackWithoutConsultants(){
		assertTrue(validator.isRollback());
	}
	//модульный тест, о котором говорили ранее, опускаем
}

Example 16. Testing transaction rollback

TransactionRollbackValidator is an EJB 3.1 component, therefore it is deployed by default using the Required transaction attribute . According to the EJB specification, the TransactionRollbackValidator # isRollback method is always executed within a transaction. Either a new transaction is launched, or an existing one is reused. In our test case, TransactionRollbackValidator invokes a new transaction in the OracleResource class . The newly launched transaction extends to the OracleResource class , which throws an EJBException due to the lack of consultants. TransactionRollbackValidator Classjust returns the result of calling the SessionContext # getRollbackOnly method .

Such a test would not have been possible without modifying the source code or without introducing additional helper classes in the src / main / java folder . Frameworks such as Arquillian provide a unique opportunity to easily test the infrastructure without cluttering the source code with testing code.

... and continuous deployment?



Testing the code outside and inside the container guarantees the correct behavior of your application only under clearly defined working conditions. Even a small server configuration change or a forgotten resource will disrupt the deployment. All modern application servers are configured either through scripts or through a truncated API. Starting the server from the configuration in your source code will not only minimize possible errors, but also make manual intervention unnecessary. You can even deploy the application to industrial servers each time you change the code in the repository. Comprehensive integration and unit tests are the first prerequisite for “continuous deployment”.

Abuse harms performance



Testing all the functionality inside an embedded container is convenient, but unproductive. Java EE 6 components are annotated Plain Old Java Objects (POJOs) and can be easily tested and mocked using Java SE tools such as JUnit, TestNG, or Mockito. Using the built-in container to test business logic is not only unproductive, but also conceptually wrong. Unit tests should only check business logic, not container behavior. In addition, most integration tests can be easily run using the local EntityManager (see example 1) or by simulating the container infrastructure. Only a small fraction of the integration tests should use built-in containers or test frameworks such as Arquillian. Although such tests are only part of the overall integration test base, these tests cover critical cases. Without an embeddable container, it is not possible to test infrastructure-dependent business logic without reconfiguring the integration environment and without polluting the source code with auxiliary testing code.

Adam Bean 's JavaOne Rock Star webinar , Testing, Deploying, Automation, and Code Quality for Java EE 7 Applications , will be held February 17 and 19

Also popular now: