Unit and Integration tests in JPA access layer
Today we are going to see how to do unit and integration tests. Focus on the data access layer, DAO, in a JPA application. But we will also see other interesting aspects of testing involving databases, such as the use of libraries for the creation of a JNDI context or even databases for testing.
To begin with, we should clarify what unit tests and integration tests are, with the help of these summarized versions of their definition:
- Unitary Test: It is the one destined to test a single component of the code. Which can be executed without the need for other dependencies.
- Integration Test: It is the one destined to test the complex functionality of the code. It will involve iteration between different components and even with external systems to the code.
Another point to take into account is that the example of this post, will be on Jakarta EE technology and not with Spring. So they will be a little more manually elaborate. In addition, when we are going to create the integration tests we will differentiate between whether we want to use a JDBC connection or access the DB through JNDI. Although both tests will be equal, this can help us to learn how to use JNDI in other integration tests that need it.
Firstly, we are going to create the application code to be tested. And the first step is to create the Entity class that will be a reflection of a table that we have in the DB.
The next step will be to create our data access class. This part will be done in two steps. First, we will create an abstract and generic class that contains the common methods. This class implements an interface that declares the methods it has.
Now, we will create the data access class associated with our entity. This will extend the previous class and will also include the dependency of the EntityManager class, which will allow us to access the data itself.
This class will be identified as an EJB component to be managed by the server through the @Stateless annotation, which will also allow us to inject it into other classes. Besides, the EntityManager class will be injected through the @PersistenceContext annotation, whose behavior is defined in the META-INF/persistence.xml file. And which will give us access to the database and transaction management.
Let’s start with the examples. First, there will be the unit test, where we will not need any DB or persistence provider. We will verify the operation of the methods through mocks. Therefore, these will be the only libraries we will need:
- Java EE specification: javax.javaee-api.
- Testing libraries: Junit for tests, Hamcrest for assertions, and Mockito for object simulation.
This method will verify the correct operation. But we will not be able to test the functioning of the EntityManager itself, its configuration, or in case of more complex queries if they are correctly developed. For all these reasons we will now perform an example of an integration test, in which we will connect to a DB, thus testing the configuration and real functioning of the queries.
To be able to carry out this type of tests, we will need the following libraries:
- A persistence provider, that is to say, the implementation of the JPA API. In this case, we will use org.eclipse.persistence.eclipselink.
- A database. For our example we will use com.h2database.h2.
The advantage of using a database, that we can store in memory, like H2, is that we will be able to create a hybrid test between unit and integration test. This is because it avoids having a connection to a real system, which can sometimes be problematic because there is no test environment or direct connection. In addition, the tests maintain their isolation and use an external stateless system, thus certifies the same behavior in each execution.
In this case, when starting the DB we want it to be populated with certain data that allows us to perform the tests. For this, we will indicate in the JDBC URL to execute the schema-generator.sql file. The rest of the configuration is quite standard.
And then we will elaborate on our integration test that will make use of this configuration.
Now we will see a modification of this last example, using instead of a JDBC connection, a connection through JNDI. But for this we will have to add a new library: com.github.h-thurow.simple-jndi. This library will allow us to populate the InitialContext object with a Datasource that we indicate so that it can be used by the Entity Manager.
The first step in this new example is to modify the persistence.xml file, to indicate that we stop using a JDBC connection and we want to use a JNDI.
For the loading of the Datasource in the context to work correctly, we must configure two files. One will be in charge of configuring the library that we have indicated previously through a file called jndi.properties. In which, we will indicate the separation characters for the nomenclature, the storage space for this information, and where the files for the different objects to be created will be located.
The second file will be the one that configures the Datasource and based on the above, it must comply with the following rules and content:
- It must be stored in the src/test/resources/jndi folder.
- It must be called jdbc.properties
- Each one of the properties of the Datasource must have the suffix myds.
All that remains is to develop the test in the following way:
We must take into account that both examples do not manage transactions because they are marked to use local transactions in the persistence.xml file. So if we want to make more complex examples, we will have to manage the transactions manually. This is because if we use JTA connections, the EntityManagerFactory class will not work.
Also note that if we want to run this kind of test at a later stage or independently, we can also do it. For it, we will have to use maven-failsafe-plugin. And we will have to follow the following instructions:
- The test must follow the following pattern: “**/IT*.java, **/*IT.java or **/*ITCase.java”.
- We must associate its execution to one of the phases of the Maven life cycle.
As you can see, we have not only learned how to test the data access layer, but we have also learned a couple of interesting details along the way. As always, you can see all the entire code in this link, where also you can see a test of each of the methods for the different examples, here.