Learnitweb

Adding a Data Layer and Unit Testing in Spring Boot with JPA, H2, and Mockito

Step 1: Add JPA and H2 Dependencies

In your pom.xml, add the following dependencies for Spring Data JPA and the H2 in-memory database:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

Step 2: Configure Spring Boot Application Properties

In src/main/resources/application.properties:

# Enable SQL query logging
spring.jpa.show-sql=true

# Enable H2 console
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console

Step 3: Create the JPA Entity

Create Item.java in the appropriate package. This entity will map to the database table.

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Transient;

@Entity
public class Item {

    @Id
    private int id;
    private String name;
    private int price;
    private int quantity;

    @Transient
    private int value;  // Not stored in the DB, used for business logic.

    // Default constructor needed by JPA
    protected Item() {}

    public Item(int id, String name, int price, int quantity) {
        this.id = id;
        this.name = name;
        this.price = price;
        this.quantity = quantity;
    }

    // Getters and setters omitted for brevity (generate them)
}

Step 4: Create the Repository Interface

Create an interface extending JpaRepository:

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

public interface ItemRepository extends JpaRepository<Item, Integer> {
    // No methods needed; JpaRepository provides CRUD operations.
}

Step 5: Create the Business Service

An example service to retrieve all items and add business logic (calculating value = price * quantity):

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

import java.util.List;

@Service
public class ItemBusinessService {

    @Autowired
    private ItemRepository repository;

    public List<Item> retrieveAllItems() {
        List<Item> items = repository.findAll();
        for (Item item : items) {
            item.setValue(item.getPrice() * item.getQuantity());
        }
        return items;
    }
}

Step 6: Create the REST Controller

Expose the business service through a controller:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class ItemController {

    @Autowired
    private ItemBusinessService businessService;

    @GetMapping("/all-items-from-database")
    public List<Item> retrieveAllItems() {
        return businessService.retrieveAllItems();
    }
}

Step 7: Unit Testing the Controller

Since controller depends on the business service, mock the business service and test controller JSON response using Spring Boot test utilities.

Example with Mockito and MockMvc (in ItemControllerTest.java):

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;

import java.util.Arrays;
import java.util.List;

import static org.hamcrest.Matchers.hasSize;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@WebMvcTest(ItemController.class)
public class ItemControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private ItemBusinessService businessService;

    @Test
    public void testRetrieveAllItems() throws Exception {
        List<Item> items = Arrays.asList(
                new Item(10001, "item1", 10, 20),
                new Item(10002, "item2", 5, 10)
        );

        // Mock the business service
        Mockito.when(businessService.retrieveAllItems()).thenReturn(items);

        mockMvc.perform(get("/all-items-from-database"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$", hasSize(2)))
            .andExpect(jsonPath("$[0].name").value("item1"))
            .andExpect(jsonPath("$[1].name").value("item2"));
    }
}

Step 7: Unit Testing the Business Service

Mock the repository and test business logic:

import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.Arrays;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;

public class ItemBusinessServiceTest {

    @Mock
    private ItemRepository repository;

    @InjectMocks
    private ItemBusinessService businessService;

    public ItemBusinessServiceTest() {
        MockitoAnnotations.openMocks(this);
    }

    @Test
    public void testRetrieveAllItems() {
        List<Item> items = Arrays.asList(
                new Item(10001, "item1", 10, 10), // expected value = 100
                new Item(10002, "item2", 20, 20)  // expected value = 400
        );

        when(repository.findAll()).thenReturn(items);

        List<Item> result = businessService.retrieveAllItems();

        assertEquals(100, result.get(0).getValue());
        assertEquals(400, result.get(1).getValue());
    }
}