spring

Testing Spring Data JPA with @DataJpaTest

Today, Unit Testing holds significant importance, and the Spring Framework offers the @DataJpaTest annotation to simplify the process of writing tests for JPA Repositories. Let us delve into understanding how to test Spring Data Jpa with @DataJpaTest annotation, followed by execution with Junit.

1. Understanding Spring Data Jpa

Spring Data JPA builds on top of the JPA specification, providing a set of abstractions and additional functionalities to streamline database access. It eliminates much of the boilerplate code developers traditionally write when implementing data access layers.

1.1 Key Features of Spring Data Jpa

Some key features of Spring Data JPA include:

  • Java Persistence API (JPA):
    • Object-Relational Mapping (ORM): JPA is a Java specification for mapping Java objects to relational database tables.
    • Entity: In JPA, an entity is a Java object that is persisted in a database. Annotated Java classes typically represent entities.
  • Spring Data JPA:
    • Repository Interface: Spring Data JPA introduces repository interfaces with methods for interacting with specific entities. The implementation is generated automatically.
    • CrudRepository and JpaRepository: Spring Data JPA extends CrudRepository with JpaRepository for additional features like pagination and sorting.
    • Query Methods: Spring Data JPA supports defining repository methods through naming conventions, automatically generating queries based on method names.
  • Entity Relationships:
    • One-to-One, One-to-Many, Many-to-One, Many-to-Many: Spring Data JPA supports defining relationships between entities using annotations on entity classes.
  • Spring Boot Integration:
    • Auto-Configuration: Spring Boot provides auto-configuration for Spring Data JPA, simplifying the setup of database connections.
    • DataSource Configuration: Spring Boot can automatically configure a DataSource based on application properties.
  • Transaction Management:
    • Transactional Support: Spring Data JPA integrates seamlessly with Spring’s transaction management, with repository methods annotated with @Transactional.
  • Auditing:
    • Entity Auditing: Spring Data JPA supports automatic auditing of entities, tracking creation/modification timestamps, and the responsible user.
  • Custom Queries:
    • JPQL and Native Queries: Developers can write custom queries using JPQL or native SQL queries, supported through annotations or method names.

2. Understanding the @DataJpaTest annotation

In the realm of Spring Framework testing, the @DataJpaTest annotation plays a crucial role in facilitating the testing of JPA repositories. This annotation provides a focused and efficient way to test the interactions between your Spring Data JPA repositories and the underlying database.

2.1 Key Features of @DataJpaTest Annotation

The @DataJpaTest annotation, when applied to a test class, triggers a configuration that specifically targets JPA components. Here are some key features of this annotation:

  • Database Configuration: @DataJpaTest auto-configures an in-memory database and configures Spring Data JPA repositories, enabling you to test repository interactions without connecting to a real database.
  • Transaction Management: Tests annotated with @DataJpaTest are transactional by default. This means that each test method is run within a transaction, and the transaction is rolled back at the end of the test, ensuring a clean slate for the next test.
  • Entity Scanning: The annotation automatically scans for JPA entities, Spring Data repositories, and other JPA-related components, reducing the need for manual configuration in your test setup.

2.2 Attributes of @DataJpaTest Annotation

The @DataJpaTest annotation in Spring provides several attributes that allow developers to customize the behavior of the test environment. Here are some of the key attributes:

  • properties: This attribute allows you to specify additional properties to be used in configuring the application context. For example:
    @DataJpaTest(properties = {"spring.jpa.properties.hibernate.format_sql=true", "spring.datasource.url=jdbc:h2:mem:testdb"})
    
  • emf: Specifies the name of the EntityManagerFactory bean to be used for the test. This is useful when you have
    multiple EntityManagerFactory beans in your application context.

    @DataJpaTest(emf = "customEntityManagerFactory")
    
  • transactional: This boolean attribute (default is true) determines whether the test should be transactional. If set to false, the test methods will not run within a transaction.
    @DataJpaTest(transactional = false)
    
  • replace: This attribute controls the extent to which the auto-configuration of the application context should be
    applied. It can be used to replace certain auto-configured components.

    @DataJpaTest(replace = AutoConfigureTestDatabase.Replace.NONE)
    
  • showSql: Determines whether SQL statements should be logged during the test. The default is false.
    @DataJpaTest(showSql = true)
    
  • useDefaultFilters: Specify whether default filters should be used to scan for components. If set to false, the default filters are disabled.
    @DataJpaTest(useDefaultFilters = false)
    

2.3 Benefits of @DataJpaTest Annotation

  • Isolation: @DataJpaTest provides a dedicated, isolated environment for testing JPA components without loading the entire Spring context.
  • Database Configuration: It automatically configures an in-memory database, ensuring that tests run against a clean and isolated database instance, reducing dependencies on external databases.
  • Entity Scan: The annotation automatically scans for JPA entities in the specified package, simplifying the configuration needed for entity management.
  • Repository Configuration: Configures and initializes only the necessary components for testing JPA repositories, leading to faster test execution.
  • TestEntityManager: Provides access to a TestEntityManager instance, allowing for convenient interaction with the underlying database during tests.
  • Transactional Behavior: Ensures that each test is executed within a transaction, and the changes made to the database during the test are rolled back, maintaining a consistent state between tests.
  • Focused Testing: Allows developers to focus on testing the data access layer in isolation, without the need to load unnecessary components, improving test efficiency.
  • Query Testing: Supports testing of repository query methods, making it convenient to verify the correctness of custom queries defined in repositories.
  • Minimal Configuration: Requires minimal configuration, reducing the effort needed to set up a testing environment for JPA components.

3. Code Example

Start by creating a new Spring Boot project. You can use Spring Initializr (https://start.spring.io/) to generate a basic project. Include dependencies for “Spring Web” and “Spring Data JPA”.

In pom.xml the following dependencies will be added during the project creation by Spring Initializr.

<!-- For Maven -->
<dependencies>
    <!-- Spring Boot Starter Data JPA -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <!-- H2 Database (in-memory database) for testing -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>test</scope>
    </dependency>
    <!-- Spring Boot Starter Test -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

3.1 Create Entity Class

Create a simple entity class, for example, Book representing a book:

package com.jcg;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;
    private String author;

    // Getters and setters

    // Constructors
}

3.2 Create Repository Interface

Create a repository interface, for example, BookRepository that extends JpaRepository:

package com.jcg;

import org.springframework.data.jpa.repository.JpaRepository;

public interface BookRepository extends JpaRepository<Book, Long> {

    Book findByTitle(String title);     

    // You can add custom queries if needed
}

3.3 Create Service Class

Create a service class, for example, BookService to perform business logic:

package com.jcg;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class BookService {

    private final BookRepository bookRepository;

    @Autowired
    public BookService(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    public List<Book> getAllBooks() {
        return bookRepository.findAll();
    }

    public Book saveBook(Book book) {
        return bookRepository.save(book);
    }
}

3.4 Implement a Main Class

Implement the main class:

package com.jcg;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringDataJpaExampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringDataJpaExampleApplication.class, args);
    }
}

3.5 Setting the Properties

Create a application.properties file in the resources folder and add the following content to it. The file will contain information about the port number on which the application should run and in-memory database properties. You’re free to change this information as per your needs.

### Server port ###
server.port=9300

### Springboot application name ###
spring.application.name=springdatajpatestexample

### Springboot default configuration properties for the h2 database ###
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

### Property to enable the console for the h2 database ###
### Browser url for h2 console - http://localhost:/h2-console
spring.h2.console.enabled=true

3.6 Create Test Class

Create a test class, for example, BookServiceTest using @DataJpaTest under src/test folder.

Make note that the @DataJpaTest annotation is used to test JPA repositories. It will configure an in-memory database, scan for @Entity classes, and configure Spring Data JPA repositories.

package com.jcg;

import org.junit.jupiter.api.Test;
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.transaction.annotation.Transactional;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

//Add the (showSql= false) attribute to the annotation for disabling the sql query logging.
@DataJpaTest
//Uncomment the below annotation to disable the transactional and rollback feature at the end of each test.
//@Transactional(propagation = Propagation.NOT_SUPPORTED)
public class BookServiceTest {

    //Allows direct interaction with the underlying JPA entity manager during testing
    @Autowired
    private TestEntityManager testEntityManager;

    @Autowired
    private BookRepository bookRepository;

    @Autowired
    private BookService bookService;

    @Test
    public void testSaveBook() {
        Book book = new Book();
        book.setTitle("Spring Boot in Action");
        book.setAuthor("Craig Walls");

        Book savedBook = bookService.saveBook(book);

        assertNotNull(savedBook.getId()); // Check if the ID is generated

        // Use TestEntityManager to retrieve the book directly from the database
        Book retrievedBook = testEntityManager.find(Book.class, savedBook.getId());

        // Assertions
        assertEquals(book.getTitle(), retrievedBook.getTitle());
        assertEquals(book.getAuthor(), retrievedBook.getAuthor());
    }

    @Test
    public void testFindByTitle() {
        // Create and persist a book
        Book book = new Book();
        book.setTitle("Clean Code");
        book.setAuthor("Robert C. Martin");
        testEntityManager.persist(book);

        // Use the repository method to find the book by title
        Book foundBook = bookRepository.findByTitle("Clean Code");

        // Assertions
        assertNotNull(foundBook);
        assertEquals(book.getTitle(), foundBook.getTitle());
        assertEquals(book.getAuthor(), foundBook.getAuthor());
    }
}

3.6.1 Output

When you run these tests via the ./mvnw test command, you can expect to see the output in the IDE console indicating the status of each test.

Test run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.123 s - in com.jcg.BookServiceTest
testSaveBook(com.example.BookServiceTest)      Time elapsed: 0.1 s
testFindByTitle(com.example.BookServiceTest)   Time elapsed: 0.023 s

Results :

Tests run: 2, Failures: 0, Errors: 0, Skipped: 0

Remember that the actual output varies based on your testing environment and configuration. The key is to look for information regarding the success or failure of each test. If all tests pass, you should see a message indicating that all tests ran successfully.

4. Conclusion

In conclusion, this comprehensive Spring Boot example showcases the seamless integration of Spring Data JPA in developing a web application. Following the steps outlined above, we have successfully created a simple yet robust system featuring an entity, a repository, and a RESTful API for performing fundamental CRUD operations. The entity, exemplified by the “Book” class, is a representation of a domain model with basic attributes such as title and author. The repository, embodied in the “BookRepository” interface, leverages the powerful abstractions provided by Spring Data JPA, offering out-of-the-box methods for database interactions. The RESTful API, encapsulated within the “BookController” class, exposes endpoints for retrieving all books, fetching a specific book by its ID, creating a new book, updating an existing book, and deleting a book. The project’s simplicity, coupled with the capabilities of Spring Boot and Spring Data JPA, underscores the framework’s efficacy in streamlining the development of robust, scalable, and maintainable applications. This example serves as a foundational guide for developers seeking to harness the capabilities of Spring Boot and Spring Data JPA in their projects, emphasizing the ease with which these technologies can be employed to build sophisticated yet straightforward solutions.

Yatin

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button