Integration testing using Testcontainers in a Spring Boot 2, JPA application

In this post we will be looking at how to perform integration testing of the repository layer using Testcontainer. We will be setting up Testcontainer in a Spring Boot 2 application, use JPA for persistence and PostgreSQL as database.Besides this we will also be using JUnit5 framework.

Why Integration testing of the database ?

In any software application, it is a known fact that data is of utmost importance. In a web application, this data will usually be handled at the database layer. In an application which uses JPA for persistence, the repository and the JPA entities are major components of the database layer.

To ensure that the repository layer and the entities are functioning appropriately, we should write unit tests. Unit tests for the same are usually written against an in memory database. This is definitely required. The only catch here – Should we wait until production to ensure that the database layer is behaving as expected against our actual type of our database as against the in memory database ?

What would be even better is if we can do integration testing against a similar database that we use in production environment – MySQL,Oracle,PostgreSQL etc. But wouldn’t setting this up be difficult and laborious for just integration testing ?

Enter Testcontainers

Using Testcontainers, we can actually start a containerized instance of the database to test our database layer without going through the pain of actually setting it up on every developer’s machine. Testcontainer is a Java library that provides disposable and lightweight instances of databases. It actually does a lot more that just that. You can read about it here.

How does it help us ?

Using JUnit tests against the actual type of our production database will give us feedback before we actually go into production.This means we can worry less about our database layer and breathe a little easy. There will always be other issues in production,NullPointerExceptions in our code that we can worry about! Since Testcontainers spawns a containerized environment for us to run our tests, we need to have Docker installed.

Getting started with the code

We will be using Spring Boot 2.3.0 along with Spring Data JPA, JUnit 5 to run the tests,Flyway to create tables and TestContainer version 1.14.1.

Snippet of the pom.xml
<!-- The PostgreSQL db driver -->        
<dependency>
	<groupId>org.postgresql</groupId>
	<artifactId>postgresql</artifactId>
	<scope>test</scope>
</dependency> 
<dependency>
	<groupId>org.testcontainers</groupId>
	<artifactId>testcontainers</artifactId>
	<version>${testcontainer.version}</version>
	<scope>test</scope>	
</dependency>
<!-- test container support for postgre module -->
<dependency>
        <groupId>org.testcontainers</groupId>
        <artifactId>postgresql</artifactId>
        <version>${testcontainer.version}</version>
        <scope>test</scope>
</dependency>
<!-- JUnit 5 integration with testcontainer -->
<dependency>
	<groupId>org.testcontainers</groupId>
	<artifactId>junit-jupiter</artifactId>
	<version>${testcontainer.version}</version>
	<scope>test</scope>
</dependency>
The application.properties file
spring.jpa.hibernate.ddl-auto=none
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQL94Dialect
spring.jpa.show-sql=true

The spring.jpa.hibernate.ddl-auto has been set to none because we are using flyway to create our database and tables. The initialization script can be found under the /db/migration folder.

Starting the containerized PostgreSQL database and tying it with Spring
public abstract class AbstractContainerBaseTest {

	protected static final PostgreSQLContainer postgres;

	static {
		postgres = new PostgreSQLContainer("postgres:9.6.15")
										 .withDatabaseName("socialmediasite")
										 .withUsername("postgres")
										 .withPassword("password");
		//Mapped port can only be obtained after container is started.
		postgres.start();
	}
	
	static class PropertiesInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
		public void initialize(ConfigurableApplicationContext configurableApplicationContext) {			
			TestPropertyValues
					.of("spring.datasource.driver-class-name=" + postgres.getDriverClassName(),
							"spring.datasource.url=" + postgres.getJdbcUrl(),
							"spring.datasource.username=" + postgres.getUsername(),
							"spring.datasource.password=" + postgres.getPassword())
					.applyTo(configurableApplicationContext.getEnvironment());
		}
	}
}

The main purpose of this abstract class is to start the container using Testcontainer library. All other test classes will extend this class .The static block creates the container and this instance will be shared by all test classes. There is a manual call to start method. The library manages to shut it down automatically.

The PropertiesInitializer class is needed because we need to access this containerized database through the Spring Data JPA.

Initialization of Testcontainer,PostgreSQL, Spring Boot,Spring Data JPA
Verifying the output
PostgreSQL container created and closed by Testcontainer

I am using the docker ps –format command to list the containers started by Testcontainer library. As shown in the image above, one can see the container having the name competent_raman which is the PostgreSQL container.For all the test cases this is the single instance of the container. There is also this additional image quay.io/testcontainers/ryuk:0.2.3 – this is created by the Testcontainer library which ensures all other containers are shut down and cleaned when the JVM shuts down.

Database and tables in the containerized PostgreSQL database.

As you can see in the image above, I am using the docker exec -it command to connect to the same containerized instance, (competent_raman) of the PostgreSQL database. Then using the command psql -h 192.168.99.100 -p 32813, we are connected to the dockerized database. The -p indicates the port number and -h the host. The \c socialmediasite command connects us to the database and finally the \d command is used to list all the tables.

Once all the tests are executed, the Testcontainer library removes the instances of the containers that it created.

The JPA entities SocialMediaSite and User used in the application have a One-Many relationship.

Conclusion

Writing unit tests against an in-memory database like H2 is certainly helpful but writing integration tests against actual type of production database takes us to the next level – more confidence, fewer failures in production. The combination of Testcontainers, Spring Boot 2, JUnit5 makes it easy for us to get started on this journey. You can find the entire source code here.

Testing JPA entities using Spring Boot 2, JUnit 5 and Java 14

In this article we will be looking at how to get started with testing JPA entities and the repository layer using Spring Boot 2.2 , JUnit 5 and Java 14. I have written a similar post here which uses Spring 1.5, JUnit 4 and Java 8. As the changes are significant I decided to keep them separate instead of updating that post.

I will be focusing mostly about the changes I had to make to the code to upgrade the libraries mentioned above. You can find the complete source code on github here.

Let us consider the same example of One-Many relation between SocialMediaSite and a User.

The @DataJpaTest

This remains the key ingredient behind running the JPA related tests in Spring Boot. @DataJpaTest disables full auto configuration and applies configuration related to JPA tests only. This concept hasn’t changed between Spring Boot 1.5 and 2.x. Now, let’s take a look at the areas where the code needed modifications.

Changes to the pom.xml

Upgrading the spring boot version to 2.2.6

<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.6.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
</parent>

Upgrading java version to 14

<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>14</java.version>
	</properties>

Upgrading the dependency section to exclude JUnit 4

The spring-boot-starter-test dependency includes the vintage JUnit 4 and JUnit 5 dependencies.Since we will be using JUnit 5, we will exclude the vintage one.

<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>

Junit 5 consists of 3 different sub projects – The JUnit engine , JUnit vintage ( Junit 3 and 4) and JUnit Jupiter( JUnit5). All JUnit 5 annotations reside in org.junit.jupiter.api package.

Support for preview features in Java 14

<plugin>
	<artifactId>maven-compiler-plugin</artifactId>
	<configuration>
		<release>14</release>
		<compilerArgs>
		    <arg>--enable-preview</arg>
		 </compilerArgs>
					 
                <forceJavacCompilerUse>true</forceJavacCompilerUse>
		  <parameters>true</parameters>
	</configuration>
</plugin>

<plugin>
	<artifactId>maven-surefire-plugin</artifactId>
	<configuration>
		<argLine>--enable-preview</argLine>
	</configuration>
</plugin>
Use of the Record type

Record type has been added as a preview feature in Java 14. The EmailAddress class has now been modified as a Record type. This was a value object before.

public record EmailAddress(String emailAddress) {

	public static final Pattern VALID_EMAIL_ADDRESS_REGEX = Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$",
			Pattern.CASE_INSENSITIVE);

	public EmailAddress {
		Assert.isTrue(isValid(emailAddress), "Email Address is Invalid !");
	}

	private static boolean isValid(String emailAddress) {
		return emailAddress != null ? VALID_EMAIL_ADDRESS_REGEX.matcher(emailAddress).matches() : false;
	}

}
Changes to the Test classes
  1. There is no need to add the @RunWith(SpringRunner.class) annotation anymore.
  2. The @Before annotation has been now replaced with @BeforeEach to perform any kind of setup before each test. Similarly the @After has been replaced with @AfterEach.
  3. The @Rule and @ExpectedException had been removed from JUnit5. The test classes have been refactored to reflect the same. The assertThrows and assertEquals method from the JUnit5 library have been used instead.
@Test
public void testShouldReturnExceptionForInvalidEmailAddress() {
		
	var exception = assertThrows(IllegalArgumentException.class, () -> new EmailAddress("test@.com"));
	assertEquals(exception.getMessage(), "Email Address is Invalid !");
}

The assertThrows method takes a functional interface, Executable as second parameter. We pass the block of code that needs to be executed to Executable.

Conclusion

The @DataJpaTest helps us to test the JPA entities and the repository layer. This is also called as test slicing as we are testing only the JPA components.The @DataJpaTest annotation contains @ExtendWith(SpringExtension.class) which integrates the Spring Testing framework with JUnit 5 model.

We have not explored many features of JUnit5 in this example but the combination of Spring Boot’s testing features and JUnit5 does make a powerful combination.

Using Spock to test JPA entities in a Spring Boot application

In my previous post we saw how to test JPA entities along with the Spring Data repository layer in a Spring Boot based application. We made use of JUnit and the AssertJ library.

In this post we will look at how to use the Spock framework to test the same. In fact, in this example, I will be using a combination of Spock based tests along with JUnit tests.To know more about the Spock framework, view the official site here. Spock is a testing and specification framework for Java and Groovy applications.

Let’s get started…

Setup using maven: ( pom.xml)  


<properties>
<spock.version>1.1-groovy-2.4</spock.version>
</properties>
<dependencies>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerId>groovy-eclipse-compiler</compilerId>
<source>1.8</source>
<target>1.8</target>
</configuration>
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-eclipse-compiler</artifactId>
<version>2.9.2-01</version>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-eclipse-batch</artifactId>
<version>2.4.3-01</version>
</dependency>
</dependencies>
</plugin>
</plugins>

  1. We need to add the spock-spring dependency. This will bring in the dependencies required to run Spock based tests in a Spring boot based application.
  2. Notice the use of <spock.version>1.1-groovy-2.4</spock.version>. We are overriding the spock version. Spring Boot 1.5.4 brings in version 1.0 of Spock, however this needs a @ContextConfiguration to run Spock based tests in a Spring Boot application. Overriding the version to 1.1 removes the need to add this annotation.
  3. In the plugin section , we need to add the dependency for groovy-eclipse-compiler which will compile the groovy code. Spock is based on groovy and hence using Spock to write tests means we write groovy code.

Test classes using Spock

As mentioned in my earlier post, let us consider the same example of 2 JPA entities, SocialMediaSite and Users ( OneToMany). A User has an email which we represented as EmailAddress value object.  The test class for this looks as follows:

EmailAddressTest.groovy


package com.boot.test.db;
import com.boot.entity.EmailAddress;
import spock.lang.Specification
public class EmailAddressTest extends Specification {
private static final String testEmail = "test@test.com";
def "throw exception for invalid format of email address"(String emailAddress)
{
when:
"create invalid email address"
def email = new EmailAddress(emailAddress);
then:
"throw exception for invalid format of email address"
def e = thrown(IllegalArgumentException)
e.message == "Email Address is Invalid !"
where:
emailAddress | _
"test@.com" | _
" " | _
null | _
}
def "return valid email address"()
{
when:
"create new email address"
def email = new EmailAddress(testEmail);
then:
"return valid email"
email.toString() == testEmail;
}
}

  1. The test class extends spock.lang.Specification. This is how you begin writing a Spock based test.
  2. Notice the method names are strings, nice descriptive methods names.
  3. The when/then syntax is for assertions. It’s like saying, “Hey, when this happens then check these things”.
  4. The where section is the first test method above is data driven.Notice the first 2 columns, emailAddress and a blank. This is because data driven tables in Spock need 2 columns. We need just one. The next rows supply data to the same method.Hence this method is run with all the values mentioned in the first column starting from the 2nd row. Now that is awesome compared to writing multiple methods which do the same thing or if you are using TestNG, this is done using a DataProvider.
  5. Notice that we have not used any Assertion library here. In Spock this is done using ==.

SocialMediaSiteRepositoryTest.groovy


package com.boot.test.db;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import com.boot.entity.EmailAddress;
import com.boot.entity.SocialMediaSite;
import com.boot.entity.User;
import com.boot.repository.SocialMediaSiteRepository;
import spock.lang.Specification
@DataJpaTest
public class SocialMediaSiteRepositoryTest extends Specification{
@Autowired
private SocialMediaSiteRepository socialMediaSiteRepository;
def facebook = new SocialMediaSite("Facebook", "Online Social Media and Networking Service")
def "find social media site FaceBook by Id" () {
def savedFaceBookEntity = socialMediaSiteRepository.save(facebook)
when: "load facebook entity"
def faceBookEntityFromDb = socialMediaSiteRepository.findOne(savedFaceBookEntity.getId())
then:"saved and retrieved entity by id must be equal"
savedFaceBookEntity.getId() == faceBookEntityFromDb.getId()
}
def "find social media site facebook by name"() {
def savedFaceBookEntity = socialMediaSiteRepository.save(facebook)
when: "find by name FaceBook"
def socialMediaEntity = socialMediaSiteRepository.findByName("Facebook")
then: "saved and retrieved entity by name must be equal"
socialMediaEntity == savedFaceBookEntity
}
def "add users to social mediate site users " () {
setup:
def firstUser = new User("Mar", "Zuckerber", new EmailAddress("mark@mark.com"));
facebook.addUser(firstUser);
def savedFaceBookEntityWithUsers = socialMediaSiteRepository.save(facebook)
when:
def faceBookEntityFromDB = socialMediaSiteRepository.findOne(savedFaceBookEntityWithUsers.getId())
then:
faceBookEntityFromDB.getUsers() != null
faceBookEntityFromDB.getUsers().size() == 1;
}
}

Notice the @DataJpaTest annotation on the class. It  spawns an in-memory data base and runs our tests  against it. Along with this, the JPA entities are scanned, Transactions, Hibernate  and Spring Data are also configured. There is no need to add @ContextConfiguration as we are using Spock 1.1.

Running Spock tests and JUnit tests together

I have added the 2 groovy test files in src/test/groovy.  We can have tests written in JUnit too. I have a JUnit based test class in src/test/java. The groovy eclipse compiler dependency we added in the pom.xml compiles and run tests from both the packages.

Conclusion

This is my first experience with Spock framework and I have thoroughly enjoyed writing tests with it ! There is of course a lot more to the Spock framework. I hope you have enjoyed this quick introduction to Spock for testing Spring Data Repository and JPA entities in a Spring Boot application. The synergy between Spock and the testing changes made in Spring Boot since version 1.4 (test slices) is great !

You can find the project on github.

Testing JPA entities in a Spring Boot application

In this blog we will look at how to get started with testing JPA entities and Spring Data Repository in a Spring Boot based application. We will be using JUnit for the same.

I have observed that a good number of projects do not write any tests for JPA entities or the repository layers which make use of the entities to perform CRUD operations. Writing tests for JPA entities and Spring data repositories can be really effective in checking if all the entities are mapped correctly and ensuring that the repository methods implemented by Spring Data along with the custom methods that you write are behaving in the right way. After all , most applications always talk to a database and if your data is not being handled properly, what is the point of having a great user interface or a well designed business layer ?

Since Spring Boot 1.4, testing these layers has become quite easy and more focused. Let us consider a simple One-Many relation between 2 entities, SocialMediaSite and  a User.

A SocialMediaSite can have many users which is mapped using the @OneToMany JPA annotation.

SocialMediaSite.java 


@Entity
public class SocialMediaSite {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String description;
@OneToMany(mappedBy = "socialMediaSite", cascade = CascadeType.ALL, orphanRemoval = true)
private List<User> users = new ArrayList<>();
//Other details.
}

User.java


@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
@Convert(converter=EmailAddressConverter.class)
private EmailAddress email;
@ManyToOne
private SocialMediaSite socialMediaSite;
//Other details.
}

view raw

User.java

hosted with ❤ by GitHub

Notice that EmailAddress is  a value object.

SocialMediaRepository.java – This is a Spring Data Repository interface( a proxy instance is created via Spring to back this interface)


public interface SocialMediaSiteRepository extends JpaRepository<SocialMediaSite, Long> {
SocialMediaSite findByName(String name);
}

SocialMediaSiteEntityTest.java –  Test the JPA entities.


package com.boot.test.db;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.context.junit4.SpringRunner;
import com.boot.entity.EmailAddress;
import com.boot.entity.SocialMediaSite;
import com.boot.entity.User;
@RunWith(SpringRunner.class)
@DataJpaTest
public class SocialMediaSiteEntityTest {
@Autowired
private TestEntityManager entityManager;
@Rule
public ExpectedException thrown = ExpectedException.none();
private SocialMediaSite facebook;
private User firstUser;
private User secondUser;
@Before
public void setUp() {
facebook = new SocialMediaSite("Facebook", "Online Social Media and Networking Service");
firstUser = new User("Mar", "Zuckerber", new EmailAddress("mark@mark.com"));
secondUser = new User("Chri", "Hughe", new EmailAddress("chris@chris.com"));
}
@Test
public void saveSocialMediaSiteFacebook() {
SocialMediaSite savedFacebookData = this.entityManager.persistAndFlush(facebook);
assertThat(savedFacebookData.getName()).isEqualTo("Facebook");
}
@Test
public void createSocialMediaSiteFacebookNullNameException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Social Media Site Name should not be empty");
new SocialMediaSite("", "Online Social Media and Networking Service");
}
@Test
public void saveFacebookUsers() {
facebook.addUser(firstUser);
facebook.addUser(secondUser);
SocialMediaSite savedFaceBook = this.entityManager.persistFlushFind(facebook);
assertThat(savedFaceBook.getId()).isNotNull();
assertThat(savedFaceBook.getName()).isEqualTo("Facebook");
assertThat(savedFaceBook.getUsers().size()).isEqualTo(2);
}
@Test
public void removeFacebookUserChri() {
facebook.addUser(firstUser);
facebook.addUser(secondUser);
SocialMediaSite savedFaceBook = this.entityManager.persistFlushFind(facebook);
savedFaceBook.removeUser(secondUser);
SocialMediaSite updatedData = this.entityManager.persistAndFlush(savedFaceBook);
assertThat(updatedData.getUsers().size()).isEqualTo(1);
assertThat(updatedData.getUsers().get(0).getFirstName()).isEqualTo("Mar");
}
}

The setUp method annotated with the @Before annotation above initializes some mock data that we can use for the tests.

The key takeaways from this class:

  1.  @RunWith(SpringRunner .class) – This brings together JUnit and the Spring test  module. The  SpringRunner class extends SpringJUnit4ClassRunner, so it is pretty  much the  same that was used earlier. Shorter class names are always pleasing to  the eye.
  2.  @DataJpaTest   This is the most important annotation for testing JPA entities in a  Spring Boot application. It  spawns an in-memory data base and runs our tests  against it. Along with this, the  JPA entities are scanned, Transactions, Hibernate  and Spring Data are also configured.
  3.  TestEntityManager   The @DataJpaTest also configures a TestEntityManager, this  is an alternative to the EntityManager. It actually makes use of the EntityManager  but has a nice set of methods like persistAndGetId, persistAndFlush etc.
  4.  AssertJ  The code above uses the AssertJ library to perform all the assertions, this  is a nice way to get all the assertions done very fluently ! This is pulled in by the  spring-boot-starter-test dependency.
  5.  Junit – This is also pulled in by the spring-boot-starter-test dependency

On similar lines, the tests for the Repository class can also be written as shown below.

SocialMediaSiteRepositoryTest .java


package com.boot.test.db;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.boot.entity.EmailAddress;
import com.boot.entity.SocialMediaSite;
import com.boot.entity.User;
import com.boot.repository.SocialMediaSiteRepository;
@RunWith(SpringRunner.class)
@DataJpaTest
public class SocialMediaSiteRepositoryTest {
@Autowired
private SocialMediaSiteRepository socialMediaSiteRepository;
private SocialMediaSite facebook;
@Before
public void setUp() {
facebook = new SocialMediaSite("Facebook", "Online Social Media and Networking Service");
}
@Test
public void saveFacebookAndFindById() {
facebook = socialMediaSiteRepository.save(facebook);
assertThat(socialMediaSiteRepository.findOne(facebook.getId())).isEqualTo((facebook));
}
@Test
public void saveFacebookAndFindBySocialMediaSiteName() {
facebook = socialMediaSiteRepository.save(facebook);
assertThat(socialMediaSiteRepository.findByName("Facebook")).isEqualTo((facebook));
}
@Test
public void saveFacebookUsers() {
User firstUser = new User("Mar", "Zuckerber", new EmailAddress("mark@mark.com"));
facebook.addUser(firstUser);
facebook = socialMediaSiteRepository.save(facebook);
// user added is not null
assertThat(socialMediaSiteRepository.findOne(facebook.getId()).getUsers()).isNotNull();
// check the one user we added.
assertThat(socialMediaSiteRepository.findOne(facebook.getId()).getUsers().size()).isEqualTo(1);
}
}

Conclusion

As you can see, testing JPA entities and the repository layer in a Spring Boot based application is quite simple. We don’t need configuration for the entire application(all layers) to test the database related functionality. Using the @DataJpaTest in Spring boot helps us in configuring and testing just the JPA section in the code.

Writing good tests for the JPA/Hibernate entities and the repository layer against an embedded database can be extremely useful in the long run. Any changes in the database schema or in the entity mapping which might lead to issues at run time can be caught immediately. In addition one can also see the SQL queries being executed which can be extremely useful.

You can find the code on github.

Note: In case you are are interested in testing JPA entities using Spring Boot 2, JUnit 5 and Java 14 , read my post here .