Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Repository tests: @DataJpaTest

Name-based queries and @Query are easy to break with a typo in a condition — the application starts but finds the wrong things. They are worth testing separately. There is no need to start the whole application with its web part and security for that. The @DataJpaTest annotation creates a slice — only what is needed for database work: entities, repositories, Flyway and transactions.

A repository test

// src/test/java/com/example/blog/PostRepositoryTests.java
package com.example.blog;
 
import static org.assertj.core.api.Assertions.assertThat;
 
import java.time.Instant;
import java.util.List;
 
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
 
@DataJpaTest
class PostRepositoryTests {
 
    @Autowired
    private PostRepository posts;
 
    @Autowired
    private UserRepository users;
 
    @Test
    void findsPostsByTitle() {
        User anna = users.save(new User("anna", "{noop}changeme"));
        posts.save(new Post("Hello, Spring", "Text", anna, Instant.now()));
        posts.save(new Post("Java records", "Text", anna, Instant.now()));
 
        List<Post> found = posts.findByTitleContainingIgnoreCase("spring");
 
        assertThat(found).extracting(Post::getTitle).containsExactly("Hello, Spring");
    }
 
    @Test
    void searchesTitleAndBody() {
        User oleg = users.save(new User("oleg", "{noop}changeme"));
        posts.save(new Post("Spring Data JPA", "Text", oleg, Instant.now()));
 
        assertThat(posts.search("data")).hasSize(1);
        assertThat(posts.search("TEXT")).hasSize(1);
        assertThat(posts.findByAuthorUsernameOrderByCreatedAtDesc("oleg")).hasSize(1);
        assertThat(posts.findLatest(5)).hasSize(1);
    }
}

extracting(Post::getTitle) takes the titles of the found posts, and containsExactly checks that exactly one — the right one — was found. The second test checks the case-insensitive JPQL search, the query through the author relationship and the plain SQL with limit.

What @DataJpaTest does

PropertyValue
Beans createdEntities, repositories, DataSource, Flyway
Beans not createdControllers, services, security
DatabaseAn embedded in-memory one instead of the one in application.yaml
TransactionsEach test is rolled back automatically

The log shows the database swap:

INFO ... Replacing 'dataSource' DataSource bean with embedded version
INFO ... Starting embedded database: url='jdbc:h2:mem:8fea3dcb-8214-4168-a703-9500904c0505;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false', username='sa'

No test profile is needed here: the database is fresh for every run anyway. Flyway migrations are applied to it just as to the real one, so the test also checks the migration SQL.

The result

[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.094 s -- in com.example.blog.PostRepositoryTests

The slice starts in just over a second — noticeably faster than a full @SpringBootTest.

Which test to choose

What we checkAnnotation
Repository queries@DataJpaTest
Addresses, response codes, security@SpringBootTest + @AutoConfigureMockMvc
Service logic together with the database@SpringBootTest
A class without SpringPlain JUnit without Spring annotations

Common mistake

If you put @EnableCaching or @EnableScheduling on the main class with @SpringBootApplication, the slice tries to enable them too, but the needed beans are not in it:

No qualifying bean of type 'org.springframework.cache.CacheManager' available: no CacheResolver specified - register a CacheManager bean or remove the @EnableCaching annotation from your configuration.

Keep such annotations in a separate @Configuration class, for example AppConfig. The full application loads it, and the @DataJpaTest slice skips it.

Thanks for reading!

Donate to the site — we work hard for you.