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 .