Skip to main content

Testing

Overview​

In this lab, you will:

  • Create a suite of Tests to test:
    • Repository
    • Service Layer
    • Controllers
    • Exceptions

You will use project from the previous lab springboot-rest-demo.

Create Repository Tests​

In the src/test folder create a folder called repository. Inside this folder create a Class called ItemRepositoryTest.java

Now try adding the following:

  • Annotate the class with @DataJpaTest
  • This tells Spring Boot to configure an in-memory database
  • This only loads JPA components for testing

testFindAll​

  • Add a function that returns void called testFindAll()
  • Store a List<Item> in a variable called items
  • Use this variable to store the values returned from calling itemRepository.findAll()
  • Use Assertions.assertFalse to assert that items.empty() is false and provide an error message incase it's true.
  • User Assertions.assertTrue to assert items.size() >= 5 and provide an error message incase it's true.
  • Annotate the test with @Test

testFindById​

  • Add a function that returns void called testFindById()
  • Store a List<Item> in a variable called items
  • Use this variable to store the values returned from calling itemRepository.findAll()
  • Create a variable of type Item that is used to store items.get(0)
  • Create an Optional<Item> variable called found that stores the result of calling itemRepository.findById(pass first.getId()) as a param
  • Use Assertions.assertTrue found.isPresent() and pass an error message incase it's false
  • User Assertions.assertEquals to assert first.getName and found.get().getName() are equal
  • Annotate the test with @Test

testSave​

  • Create a New Item
  • Instantiate a new Item object with the name "Pear" and price 1.49.
  • Save the Item
  • Use the repository's save() method to persist the new item to the database.
  • Verify the Item Was Saved
  • Assert that the saved item has a non-null ID, confirming it was successfully stored and assigned a primary key by the database.
  • Assert that the saved item's name matches "Pear", ensuring the data was saved correctly.
  • Annotate the test with @Test

testDelete​

  • Add a function that returns void called testDelete()
  • Retrieve an Existing Item
  • Fetch all items from the repository and select the first item to delete
  • Delete the Item
  • Use the repository's deleteById() method to remove the selected item from the database by its ID
  • Verify the Item Was Deleted
  • Attempt to find the item again by its ID
  • Assert that the item is no longer present in the database, confirming successful deletion
  • Annotate the test with @Test

Repository Test Implementation​

Click here to view the example solution
import com.neueda.entity.Item;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;

import java.util.List;
import java.util.Optional;

@DataJpaTest
public class ItemRepositoryTest {

@Autowired
private ItemRepository itemRepository;

@Test
void testFindAll() {
List<Item> items = itemRepository.findAll();
Assertions.assertFalse(items.isEmpty(), "Items should not be empty");
Assertions.assertTrue(items.size() >= 5, "Should have at least 5 seeded items");
}

@Test
void testFindById() {
List<Item> items = itemRepository.findAll();
Item first = items.get(0);
Optional<Item> found = itemRepository.findById(first.getId());
Assertions.assertTrue(found.isPresent(), "Item should be found by id");
Assertions.assertEquals(first.getName(), found.get().getName());
}

@Test
void testSave() {
Item newItem = new Item("Pear", 1.49);
Item saved = itemRepository.save(newItem);
Assertions.assertNotNull(saved.getId(), "Saved item should have an id");
Assertions.assertEquals("Pear", saved.getName());
}

@Test
void testDelete() {
List<Item> items = itemRepository.findAll();
Item toDelete = items.get(0);
itemRepository.deleteById(toDelete.getId());
Optional<Item> found = itemRepository.findById(toDelete.getId());
Assertions.assertFalse(found.isPresent(), "Item should be deleted");
}
}

Service Layer Tests​

In the src/test folder create a folder called service. Inside this folder create a Class called ItemServiceTest.java

Create the Test Class​

Add Required Imports​

  • Import JUnit, Mockito, and your service/repository/entity classes

Annotate the Class for Mockito​

  • Add @ExtendWith(MockitoExtension.class) above your class definition to enable Mockito support.

Mock the Repository​

  • Use @Mock to create a mock of ItemRepository.

Inject the Service​

  • Use @InjectMocks to inject the mock repository into an instance of ItemService.

Write Test Methods​

  • Write test methods for each service function:
    • testGetAllItems: Mock findAll() and assert the returned list size.
    • testGetItemById_found: Mock findById() to return an item and assert the result.
    • testGetItemById_notFound: Mock findById() to return empty and assert that ItemNotFoundException is thrown.
    • testCreateItem: Mock save() and assert the returned item.
    • testUpdateItem_found: Mock findById() and save(), update an item, and assert the changes.
    • testUpdateItem_notFound: Mock findById() to return empty and assert exception.
    • testDeleteItem_found: Mock existsById() to return true, call delete, and verify.
    • testDeleteItem_notFound: Mock existsById() to return false and assert exception.

Example Test Method​

Click here to view the example test method
@Test
void testGetAllItems() {
List<Item> mockItems = Arrays.asList(
new Item("Apple", 0.99),
new Item("Banana", 0.59)
);
Mockito.when(itemRepository.findAll()).thenReturn(mockItems);
List<Item> items = itemService.getAllItems();
Assertions.assertEquals(2, items.size());
}

Service Test Implementation​

Click here to view the complete example solution
import com.neueda.entity.Item;
import com.neueda.exception.ItemNotFoundException;
import com.neueda.repository.ItemRepository;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.junit.jupiter.api.extension.ExtendWith;

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

@ExtendWith(MockitoExtension.class)
public class ItemServiceTest {

@Mock
private ItemRepository itemRepository;

@InjectMocks
private ItemService itemService;

@Test
void testGetAllItems() {
List<Item> mockItems = Arrays.asList(
new Item("Apple", 0.99),
new Item("Banana", 0.59)
);
Mockito.when(itemRepository.findAll()).thenReturn(mockItems);
List<Item> items = itemService.getAllItems();
Assertions.assertEquals(2, items.size());
}

@Test
void testGetItemById_found() {
Item mockItem = new Item("Apple", 0.99);
Mockito.when(itemRepository.findById(1L)).thenReturn(Optional.of(mockItem));
Item item = itemService.getItemById(1L);
Assertions.assertEquals("Apple", item.getName());
}

@Test
void testGetItemById_notFound() {
Mockito.when(itemRepository.findById(2L)).thenReturn(Optional.empty());
Assertions.assertThrows(ItemNotFoundException.class, () -> itemService.getItemById(2L));
}

@Test
void testCreateItem() {
Item toSave = new Item("Orange", 1.29);
Mockito.when(itemRepository.save(toSave)).thenReturn(toSave);
Item saved = itemService.createItem(toSave);
Assertions.assertEquals("Orange", saved.getName());
}

@Test
void testUpdateItem_found() {
Item existing = new Item("Apple", 0.99);
Item update = new Item("Green Apple", 1.09);
Mockito.when(itemRepository.findById(1L)).thenReturn(Optional.of(existing));
Mockito.when(itemRepository.save(existing)).thenReturn(existing);
Item updated = itemService.updateItem(1L, update);
Assertions.assertEquals("Green Apple", updated.getName());
Assertions.assertEquals(1.09, updated.getPrice());
}

@Test
void testUpdateItem_notFound() {
Item update = new Item("Green Apple", 1.09);
Mockito.when(itemRepository.findById(2L)).thenReturn(Optional.empty());
Assertions.assertThrows(ItemNotFoundException.class, () -> itemService.updateItem(2L, update));
}

@Test
void testDeleteItem_found() {
Mockito.when(itemRepository.existsById(1L)).thenReturn(true);
Assertions.assertDoesNotThrow(() -> itemService.deleteItem(1L));
Mockito.verify(itemRepository).deleteById(1L);
}

@Test
void testDeleteItem_notFound() {
Mockito.when(itemRepository.existsById(2L)).thenReturn(false);
Assertions.assertThrows(ItemNotFoundException.class, () -> itemService.deleteItem(2L));
}
}

Controller Tests​

In the src/test folder create a folder called controller. Inside this folder create a Class called ItemControllerTest.java

testGetAllItems​

Purpose: Verifies that the /items endpoint returns a list of items.

How it works:

  • No service or repository mocking is needed; the test uses the real repository with an in-memory database.
  • Sends a GET request to /items.
  • Asserts:
    • HTTP status is 200 OK.
    • The first item in the returned JSON array has a name property.

testGetItemById​

Purpose: Checks that you can retrieve a specific item by its ID.

How it works:

  • Saves a new item to the repository (which will be used by the service/controller).
  • Sends a GET request to /items/{id} for that item.
  • Asserts:
    • HTTP status is 200 OK.
    • The returned JSON object has a name property matching the saved item.

testCreateItem​

Purpose: Ensures that you can create a new item via the API.

How it works:

  • Creates a new Item object and serializes it to JSON.
  • Sends a POST request to /items with the item data.
  • The controller calls the service, which saves the item using the repository.
  • Asserts:
    • HTTP status is 200 OK.
    • The returned JSON object has a name property matching the input.

testUpdateItem​

Purpose: Verifies that you can update an existing item.

How it works:

  • Saves an item to the repository.
  • Creates an updated Item object and serializes it to JSON.
  • Sends a PUT request to /items/{id} with the updated data.
  • The controller calls the service, which updates the item in the repository.
  • Asserts:
    • HTTP status is 200 OK.
    • The returned JSON object has a name property matching the update.

testDeleteItem​

Purpose: Checks that you can delete an item by its ID.

How it works:

  • Saves an item to the repository.
  • Sends a DELETE request to /items/{id}.
  • The controller calls the service, which deletes the item from the repository.
  • Asserts:
    • HTTP status is 204 No Content.
    • The item is no longer present in the repository (checked with findById).

testGetItemById_NotFound​

Purpose: Ensures the API returns a 404 status and an error message when an item is not found.

How it works:

  • Sends a GET request to /items/99999 (an ID that doesn't exist).
  • The controller calls the service, which throws ItemNotFoundException.
  • Asserts:
    • HTTP status is 404 Not Found.
    • The response body contains the string "not found".

testCreateItem_InvalidParams​

Purpose: Checks that the API returns a 400 status and an error message when required parameters are missing.

How it works:

  • Creates an item with null values for name and price.
  • Sends a POST request to /items.
  • The controller checks the parameters and throws InvalidParamsException.
  • Asserts:
    • HTTP status is 400 Bad Request.
    • The response body contains the string "must be provided".

Note:​

  • These tests use the real repository and service layer, not mocks, because they are annotated with @SpringBootTest and use an in-memory database.
  • Each test checks both the HTTP status and the content of the response to ensure the controller and service logic work as expected.

Controller Test Implementation​

Click here to view the complete example solution
import com.fasterxml.jackson.databind.ObjectMapper;
import com.neueda.entity.Item;
import com.neueda.repository.ItemRepository;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureMockMvc
public class ItemControllerTest {

@Autowired
private MockMvc mockMvc;

@Autowired
private ObjectMapper objectMapper;

@Autowired
private ItemRepository itemRepository;

@Test
void testGetAllItems() throws Exception {
mockMvc.perform(get("/items"))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].name").exists());
}

@Test
void testGetItemById() throws Exception {
Item item = itemRepository.save(new Item("TestItem", 2.99));
mockMvc.perform(get("/items/" + item.getId()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("TestItem"));
}

@Test
void testCreateItem() throws Exception {
Item item = new Item("CreatedItem", 3.99);
String json = objectMapper.writeValueAsString(item);
mockMvc.perform(post("/items")
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("CreatedItem"));
}

@Test
void testUpdateItem() throws Exception {
Item item = itemRepository.save(new Item("OldName", 1.99));
Item update = new Item("UpdatedName", 2.99);
String json = objectMapper.writeValueAsString(update);
mockMvc.perform(put("/items/" + item.getId())
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("UpdatedName"));
}

@Test
void testDeleteItem() throws Exception {
Item item = itemRepository.save(new Item("ToDelete", 0.99));
mockMvc.perform(delete("/items/" + item.getId()))
.andExpect(status().isNoContent());
Assertions.assertFalse(itemRepository.findById(item.getId()).isPresent());
}

@Test
void testGetItemById_NotFound() throws Exception {
mockMvc.perform(get("/items/99999"))
.andExpect(status().isNotFound())
.andExpect(content().string(org.hamcrest.Matchers.containsString("not found")));
}

@Test
void testCreateItem_InvalidParams() throws Exception {
Item item = new Item(null, null);
String json = objectMapper.writeValueAsString(item);
mockMvc.perform(post("/items")
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andExpect(status().isBadRequest())
.andExpect(content().string(org.hamcrest.Matchers.containsString("must be provided")));
}
}

Running the Tests​

You can run all tests using:

Maven:

mvn test

Gradle:

./gradlew test

IDE:

  • Right-click on the test class or method and select "Run"
  • Use the test runner built into your IDE

Test Types Summary​

  1. Repository Tests (@DataJpaTest):

    • Test JPA repository methods
    • Use in-memory database
    • Test CRUD operations
  2. Service Tests (@ExtendWith(MockitoExtension.class)):

    • Unit tests with mocked dependencies
    • Test business logic in isolation
    • Mock repository calls
  3. Controller Tests (@SpringBootTest + @AutoConfigureMockMvc):

    • Integration tests
    • Test HTTP endpoints
    • Use real service and repository layers
    • Test request/response handling and exception scenarios

Purpose: This comprehensive testing approach ensures your Spring Boot application works correctly at all layers, from database access to HTTP API endpoints.