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.assertFalseto assert thatitems.empty()is false and provide an error message incase it's true. - User
Assertions.assertTrueto assertitems.size() >= 5and 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 callingitemRepository.findById(pass first.getId())as a param - Use
Assertions.assertTrue found.isPresent()and pass an error message incase it's false - User
Assertions.assertEqualsto assertfirst.getNameandfound.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
@Mockto create a mock ofItemRepository.
Inject the Service
- Use
@InjectMocksto inject the mock repository into an instance ofItemService.
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 thatItemNotFoundExceptionis thrown. - testCreateItem: Mock
save()and assert the returned item. - testUpdateItem_found: Mock
findById()andsave(), 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.
- testGetAllItems: Mock
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
nameproperty.
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
nameproperty matching the saved item.
testCreateItem
Purpose: Ensures that you can create a new item via the API.
How it works:
- Creates a new
Itemobject and serializes it to JSON. - Sends a POST request to
/itemswith 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
nameproperty 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
Itemobject 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
nameproperty 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
nullvalues 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
@SpringBootTestand 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
-
Repository Tests (
@DataJpaTest):- Test JPA repository methods
- Use in-memory database
- Test CRUD operations
-
Service Tests (
@ExtendWith(MockitoExtension.class)):- Unit tests with mocked dependencies
- Test business logic in isolation
- Mock repository calls
-
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.