CodeGym /Courses /Spring Test /@DataJpaTest: data slice for repositories

@DataJpaTest: data slice for repositories

Spring Test
Level 14, Lesson 0
Available

1. The role of repository tests

After a few intense days with @WebMvcTest, you get the feeling: “Well, that’s it, we already tested the API — so the system must be reliable.” And right there, the repository quietly smiles in the corner like a cat that just knocked over a flowerpot but is acting like it had nothing to do with it. The persistence layer breaks in its own way: not like HTTP, not like JSON, and definitely not like plain business logic in unit tests.

In a web test, we verify that the controller accepted the request correctly, called the service, and returned the right status/JSON. But a web test usually does not prove that data is actually saved and read the way we expect. Even if you mocked the service carefully, there is still a huge layer of uncertainty: is the JPA mapping correct? does nullable=false really work? does the repository actually find the article by slug, and not “by inspiration”?

In ContentHub, this is especially noticeable in the basics: Article stores important fields (slug, status, author, timestamps), and we need to trust that writing to the DB and reading from it behave consistently. That is why we introduce a separate, honest, and focused layer: repository tests.

2. Repository layer in ContentHub: not just CRUD

The repository layer is often underestimated because Spring Data JPA looks like magic: write an interface — and it “just works.” But that “just” works only until you rename a field, add a constraint, update a relationship mapping, or create an “obvious” derived method that turns out not to be so obvious. And then the bug shows up not as a nice exception with a hint, but as “nothing is found,” “it fails on save,” “the SQL is different in prod,” and “why did this pass review?”

In our ContentHub project, the repository layer is where interfaces like ArticleRepository live, responsible for reading and writing Article (and later — attachments, categories, and more). A repository is the boundary between the Java model and the database. It promises: “You give me an entity — I write it to tables. You give me a search criterion — I return what is in the DB.” And tests at this level are needed to verify exactly that promise.

For context, a repository usually looks something like this (highly simplified, but correct in spirit):

import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ArticleRepository extends JpaRepository<Article, Long> {

    // Find an article by business key (slug), not by technical id
    // Spring Data will generate the query from the method name
    Optional<Article> findBySlug(String slug);
}

Notice: there is no HTTP, no DTO, no controllers here. This is a “pure” persistence boundary. And testing it through @WebMvcTest is like checking how a refrigerator works by opening only the door and celebrating that the light turns on.

3. @DataJpaTest as a test slice

In previous modules, we got used to the idea that Spring Boot can bring up narrow slices of the context (slices), and that makes tests much faster and more honest. For JSON contracts, we had @JsonTest; for the controller boundary, @WebMvcTest. It is only logical that there is also a specialized tool for the persistence boundary, instead of “let’s start the whole application and somehow test the repository inside @SpringBootTest.”

@DataJpaTest is exactly that kind of tool: a narrow data slice that brings up only what is needed for JPA and repositories. It exists to quickly and predictably verify how @Entity classes, their mappings, and Spring Data repositories behave. In other words, we are not testing “the whole application,” but a specific subsystem: the persistence layer.

And this is an important mental shift: @DataJpaTest is not about “well, at least some test.” It is about discipline: every layer has its own proof boundary. A data slice proves that the data storage layer is alive, sane, and not lying to our face (at least — not intentionally).

4. The contents of the @DataJpaTest context

When you put @DataJpaTest on a test class, Spring Boot assembles a small context focused on JPA. It helps to think of it not as an “annotation,” but as a “test environment startup mode.” In this mode, you get everything needed to actually work with a database through JPA, but you do not get the extra pieces that slow down the suite and mix responsibilities.

Let’s capture this in a small table — it often saves you from the most common expectations like “why isn’t my service injected? it is such a good service”:

Application part Included by default in @DataJpaTest Plain-language comment
@Entity classes (JPA entities) Yes Otherwise there is nothing to map into tables
Spring Data JPA repositories (JpaRepository and so on) Yes This is the main object under test
EntityManager / JPA infrastructure Yes Hibernate (or another provider) must work
DataSource Yes A database is required, even if it is a test one
Transactions for tests Usually yes As a rule, tests run in a transaction (details — in the next lecture)
@Controller, @RestController No This is the web layer; it is unnecessary here
@Service and orchestration logic No This is the business layer; it is tested differently
Security config / filters No This is a separate risk layer; do not mix it in
MockMvc, HTTP, response serialization No That was covered in @WebMvcTest

In practice, this means that @DataJpaTest is a very fast way to tell Spring: “Start exactly enough of the application so I can test repositories and JPA mapping. And please do not drag in controllers, security, and the whole zoo of configuration.”

And one more important point: if an embedded database driver is on the classpath (for example, H2), Spring Boot can often bring up a test database automatically. That is convenient for starting out and for quick checks. At the same time, we do not pretend that an embedded DB is 100% the same as PostgreSQL — we just use it as a fast training ground. But without getting ahead of ourselves: today we need to understand the slice itself and its boundaries.

5. The minimal data-slice test skeleton

The transition from web tests to data tests often feels like changing professions: yesterday you were building HTTP requests, and today you suddenly think in terms of entities and tables. That is normal. The good news: you can start very simply. @DataJpaTest does most of the heavy lifting for you, and all that is required from you is a clean test structure and a clear understanding of what exactly you are testing.

The minimal repository test skeleton looks like this:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;

@DataJpaTest // Bring up only the JPA infrastructure and Spring Data repositories
class ArticleRepositoryDataJpaTest {

    @Autowired
    private ArticleRepository articleRepository; // This is the "object under test": a real repository, not a mock
}

And this is not a “placeholder for show.” Even at this stage, it is important to notice: we are not starting ContentHubApplication, we are not launching a server, and we are not touching controllers. This is a persistence-level test.

If you are used to replacing services in @WebMvcTest with @MockitoBean, then the idea here is different: you do not mock the repository, you verify the repository’s real behavior. Mocking a repository in a repository test is like testing a bicycle by drawing it with a marker on paper. Formally the wheels are round, but it will not move.

6. Test: saving Article

The first data-slice test often feels like it should be “big and smart”: let’s immediately test saving, reading, searching by slug, updating, deleting, and put everything in one method so we do not waste time. Tests have one unpleasant property: they are read more often than they are written. So the first correct step is to make the test small and obvious: prove that we can actually write an article through the repository.

The simplest scenario looks like this: create a valid Article, save it, and make sure the saved entity has an id. The appearance of the id is not just cosmetics. It is the minimal sanity check that the entity passed through the JPA layer successfully and got a primary key. But this is still not the case where we deliberately prove a physical round-trip to the DB: inside a single persistence context, JPA controls a lot on its own. For now, we only need to see a simpler fact — the data slice is alive and the repository can save a valid entity. When we need to strictly separate managed state from changes actually written to the DB, flush and clear will come into play.

Example (with a small helper method for readability):

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;

@Test
void savesArticleInsideDataSlice() {
    // Act: save the entity through the real Spring Data repository
    Article saved = articleRepository.save(validArticle("spring-data-basics"));

    // Assert: if an id appears, then basic persistence through the JPA layer worked
    assertThat(saved.getId()).isNotNull();
}

Here, validArticle(...) is just shorthand for a minimally valid ContentHub article. In a real test class, it is more convenient to build such a fixture through separate helper methods and include the category right away, so that all tests rely on the same valid article model.

7. Embedded database in @DataJpaTest: auto-start without panic

When you run @DataJpaTest for the first time, there are usually two typical feelings. First: “Oh, it brought up the database by itself — I am a genius.” Second (usually after 30 seconds): “Wait, which database did it bring up? I did not configure anything.” Both feelings are normal. Spring Boot really tries to make the data slice as convenient as possible: if an embedded DB is on the test classpath, it can use it automatically as the test database.

The idea here is simple: repository tests should be fast and local, otherwise they stop being repository tests and turn into “mini integration with the whole world.” An embedded database helps you get quick feedback and catch basic mapping and repository-method issues without requiring the student (or the team) to set up heavy infrastructure right from the first lecture of the data module.

But it is important to keep the right expectation: an embedded DB is a learning and fast mode, not “the absolute truth about prod.” We use it as a convenient starting point to understand the mechanics and learn how to write careful repository tests. The real differences between databases are a separate topic, and we will not turn the first data-day lecture into a “war of SQL dialects.”

8. Data slice boundaries

It is very easy to turn a data-slice test into a “universal combine harvester”: pull in services, trigger a workflow, check business status transitions, and at the same time make sure the controller returns 200 OK. Technically you can try that, but the point of the slice approach will be destroyed roughly the same way a diet is destroyed by the phrase “well, it is just a small piece of cake.” A data slice exists precisely so that you do not mix different responsibility zones.

In @DataJpaTest, we do not verify HTTP contracts, compare JSON, evaluate validation messages, or test @ControllerAdvice. All of that was already covered (and correctly so) in the web layer. We also do not bring in PublicationPolicy rules and orchestration from ArticleWorkflowService here, because they belong to business logic and are covered by unit tests and, later, integration scenarios.

A data-slice test answers more down-to-earth, but critically important questions: “Can I save an entity in the DB?”, “Can I retrieve it back?”, “Does what I search by key field (for example, slug) actually get found?” And there is a beauty in that: the test becomes short, honest, and very easy to diagnose. It fails not because “the whole application did not start,” but because a specific part of the persistence layer behaves incorrectly.

9. Common mistakes when first getting to know @DataJpaTest

Mistake #1: expecting controllers and services to be available in @DataJpaTest.
This is a common habit after @SpringBootTest: you want “everything at hand.” But a data slice intentionally limits the context. If you need a service, that is a different test level. If you drag a service into a data test, you risk starting to test business logic through the DB, and that is an expensive and confusing path.

Mistake #2: trying to test HTTP semantics in a repository test.
In a repository test there are no 200/400 statuses, no JSON, no MockMvc. And that is not an “inconvenience,” but protection from mixing layers. If you catch yourself wanting to verify Content-Type in @DataJpaTest, you probably just opened the wrong door.

Mistake #3: importing half the configuration into @DataJpaTest “to make it work.”
Sometimes a student sees a binding error and decides: “Fine, I will add @Import(MyHugeConfig.class).” The test may indeed start working, but you lose the main advantage of slice tests: speed, locality, and clear boundaries. In the end, you get a small @SpringBootTest, only worse and without an honest name.

Mistake #4: mocking the repository in a repository test.
This looks paradoxical, but it happens: “I am used to mocking dependencies, let’s mock ArticleRepository.” Then the test will verify... your mock. That is, your imagination of how the repository should behave. It is not a bad genre of literature, but it is a weak genre of testing.

Mistake #5: writing one huge test “for everything” instead of several small ones.
A large test quickly turns into a hard-to-read scenario that fails “somewhere in the middle.” On the data layer, it is especially important to keep tests short: setup — action — assertion. The less extra story you have in a test, the easier it is to understand what actually broke.

1
Task
Spring Test, level 14, lesson 0
Locked
Saving a book in a data slice
Saving a book in a data slice
1
Task
Spring Test, level 14, lesson 0
Locked
Search subscriber by email in `@DataJpaTest`
Search subscriber by email in `@DataJpaTest`
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION