Skip to content

Commit fabccec

Browse files
authored
test(book): refactor tests to use ArgumentCaptor and ParameterizedTest (#5)
* Update success test to use ArgumentCaptor + special chars (extra redundant test) * Combine 'Bad title' tests to @ParameterizedTest + @nullAndEmptySource * Rename variables for better clarity for others
1 parent 3ccb4f8 commit fabccec

File tree

1 file changed

+72
-108
lines changed

1 file changed

+72
-108
lines changed

src/test/java/com/codesungrape/hmcts/bookapi/BookServiceTest.java

Lines changed: 72 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
import org.junit.jupiter.params.ParameterizedTest;
1212
import org.junit.jupiter.params.provider.Arguments;
1313
import org.junit.jupiter.params.provider.MethodSource;
14+
import org.junit.jupiter.params.provider.NullAndEmptySource;
15+
import org.junit.jupiter.params.provider.ValueSource;
16+
import org.mockito.ArgumentCaptor;
1417
import org.mockito.InjectMocks;
1518
import org.mockito.Mock;
1619
import org.mockito.junit.jupiter.MockitoExtension;
@@ -20,7 +23,7 @@
2023
import java.util.stream.Stream;
2124

2225
import static org.junit.jupiter.api.Assertions.assertEquals;
23-
import static org.junit.jupiter.api.Assertions.assertNotNull;
26+
import static org.junit.jupiter.api.Assertions.assertNull;
2427
import static org.junit.jupiter.api.Assertions.assertThrows;
2528
import static org.junit.jupiter.api.Assertions.assertTrue;
2629
import static org.mockito.ArgumentMatchers.any;
@@ -120,24 +123,40 @@ void setUp() {
120123
// --------------------------------------
121124
// Tests: createBook
122125
// --------------------------------------
126+
123127
@Test
124128
void testCreateBook_Success() {
129+
// Arrange: also checks special chars
130+
BookRequest specialRequest =
131+
new BookRequest("Java & Friends!", "Synopsis", "Author");
125132

126-
// Arrange: tell the mock repository what to do when called
127-
when(testBookRepository.save(any(Book.class))).thenReturn(persistedBook);
133+
// The "Future" Book (What the DB returns)
134+
Book bookFromDb =
135+
Book.builder()
136+
.id(testId)
137+
.title(specialRequest.title())
138+
.synopsis(specialRequest.synopsis())
139+
.author(specialRequest.author())
140+
.build();
128141

129-
// Act: call the service method we are testing
130-
Book result = testBookService.createBook(validBookRequest);
142+
when(testBookRepository.save(any(Book.class))).thenReturn(bookFromDb);
131143

132-
// Assert: Check the outcome
133-
assertNotNull(result);
134-
assertEquals(testId, result.getId());
135-
assertEquals(validBookRequest.title(), result.getTitle());
136-
assertEquals(validBookRequest.synopsis(), result.getSynopsis());
137-
assertEquals(validBookRequest.author(), result.getAuthor());
144+
// Act
145+
Book result = testBookService.createBook(specialRequest);
146+
147+
// Assert: Verify the Service fulfills its return contract
148+
assertEquals(bookFromDb, result, "Service must return the object returned by the repository");
149+
150+
// Assert: Capture the "Past" Book (What went INTO the DB)
151+
ArgumentCaptor<Book> bookCaptor = ArgumentCaptor.forClass(Book.class);
152+
verify(testBookRepository, times(1)).save(bookCaptor.capture());
153+
Book bookSentToDb = bookCaptor.getValue();
138154

139-
// Did the service perform the correct action on its dependency?
140-
verify(testBookRepository, times(1)).save(any(Book.class));
155+
// Assert
156+
assertNull(bookSentToDb.getId(), "ID should be null before DB generates it");
157+
assertEquals(specialRequest.title(), bookSentToDb.getTitle());
158+
assertEquals(specialRequest.synopsis(), bookSentToDb.getSynopsis());
159+
assertEquals(specialRequest.author(), bookSentToDb.getAuthor());
141160
}
142161

143162
@Test
@@ -151,51 +170,6 @@ void testCreateBook_NullRequest_ThrowsException() {
151170
);
152171
}
153172

154-
@Test
155-
void testCreateBook_NullTitle_ThrowsException() {
156-
// Arrange
157-
BookRequest invalidRequest = new BookRequest(null, "Synopsis", "Author");
158-
159-
// Act & Assert
160-
assertThrows(
161-
IllegalArgumentException.class,
162-
() -> {
163-
testBookService.createBook(invalidRequest);
164-
}
165-
);
166-
167-
// Verify repository was never called
168-
verify(testBookRepository, never()).save(any());
169-
}
170-
171-
@Test
172-
void testCreateBook_EmptyTitle_ThrowsException() {
173-
// Arrange
174-
BookRequest invalidRequest = new BookRequest("", "Synopsis", "Author");
175-
176-
// Act & Assert
177-
assertThrows(
178-
IllegalArgumentException.class,
179-
() -> {
180-
testBookService.createBook(invalidRequest);
181-
}
182-
);
183-
}
184-
185-
@Test
186-
void testCreateBook_BlankTitle_ThrowsException() {
187-
// Arrange
188-
BookRequest invalidRequest = new BookRequest(" ", "Synopsis", "Author");
189-
190-
// Act & Assert
191-
assertThrows(
192-
IllegalArgumentException.class,
193-
() -> {
194-
testBookService.createBook(invalidRequest);
195-
}
196-
);
197-
}
198-
199173
@Test
200174
void testCreateBook_RepositoryFailure_ThrowsException() {
201175
// Arrange
@@ -211,6 +185,19 @@ void testCreateBook_RepositoryFailure_ThrowsException() {
211185
);
212186
}
213187

188+
@ParameterizedTest
189+
@NullAndEmptySource // Covers Null and "" (Empty)
190+
@ValueSource(strings = {" ", "\t", "\n"}) // Covers Blank (Spaces/Tabs)
191+
void testCreateBook_InvalidTitle_ThrowsException(String invalidTitle) {
192+
// Arrange
193+
BookRequest invalidRequest = new BookRequest(invalidTitle, "Synopsis", "Author");
194+
195+
// Act & Assert
196+
assertThrows(IllegalArgumentException.class, () -> {
197+
testBookService.createBook(invalidRequest);
198+
});
199+
}
200+
214201
// ----- EDGE cases ---------
215202

216203
@ParameterizedTest(name = "{0}") // Display the test name
@@ -220,51 +207,24 @@ void testCreateBook_VeryLongFields_Success(
220207
BookRequest request,
221208
Book expectedBook
222209
) {
223-
224210
// Arrange
225211
when(testBookRepository.save(any(Book.class))).thenReturn(expectedBook);
226212

227213
// Act
228214
Book result = testBookService.createBook(request);
229215

230-
// Assert
231-
assertNotNull(result);
232-
assertEquals(expectedBook.getId(), result.getId());
233-
assertEquals(expectedBook.getTitle(), result.getTitle());
234-
assertEquals(expectedBook.getSynopsis(), result.getSynopsis());
235-
assertEquals(expectedBook.getAuthor(), result.getAuthor());
236-
237-
verify(testBookRepository, times(1)).save(any(Book.class));
238-
}
239-
240-
@Test
241-
void testCreateBook_SpecialCharactersInTitle_Success() {
242-
// Arrange
243-
BookRequest specialRequest =
244-
new BookRequest("Test: A Book! @#$%^&*()", "Synopsis", "Author");
245-
246-
Book expectedBook =
247-
Book.builder()
248-
.id(testId)
249-
.title(specialRequest.title())
250-
.synopsis(specialRequest.synopsis())
251-
.author(specialRequest.author())
252-
.build();
216+
// Assert 1: Verify the result flow
217+
assertEquals(expectedBook, result);
253218

254-
when(testBookRepository.save(any(Book.class))).thenReturn(expectedBook);
219+
// --- CAPTOR LOGIC ---
220+
ArgumentCaptor<Book> bookCaptor = ArgumentCaptor.forClass(Book.class);
221+
verify(testBookRepository).save(bookCaptor.capture());
222+
Book bookSentToDb = bookCaptor.getValue();
255223

256-
// Act
257-
Book result = testBookService.createBook(specialRequest);
258-
259-
// Assert
260-
assertNotNull(result);
261-
assertEquals(testId, result.getId());
262-
assertEquals(specialRequest.title(), result.getTitle());
263-
assertEquals(specialRequest.synopsis(), result.getSynopsis());
264-
assertEquals(specialRequest.author(), result.getAuthor());
265-
266-
// Did the service perform the correct action on its dependency?
267-
verify(testBookRepository, times(1)).save(any(Book.class));
224+
// Assert 2: Verify the Service correctly mapped the LONG strings
225+
// This ensures the service didn't truncate/alter the data
226+
assertEquals(request.title(), bookSentToDb.getTitle());
227+
assertEquals(request.synopsis(), bookSentToDb.getSynopsis());
268228
}
269229

270230
// --------------------------------------------------------------------------------------------
@@ -274,13 +234,11 @@ void testCreateBook_SpecialCharactersInTitle_Success() {
274234
@Test
275235
void testDelete_Book_ShouldThrowException_WhenIdNotFound() {
276236

277-
// Arrange: As goal is to test what happens when the resource doesn't exist,
278-
// we intentionally simulate DB returning NO result
237+
// Arrange: simulate missing record
279238
when(testBookRepository.findById(testId)).thenReturn(Optional.empty());
280239

281-
// ACT and ASSERT: throw ResourceNotFoundException when calling the delete method.
240+
// ACT and ASSERT: correct exception thrown
282241
assertThrows(
283-
// custom exception to reflect business rules vs technical problem
284242
ResourceNotFoundException.class,
285243
() -> testBookService.deleteBookById(testId)
286244
);
@@ -293,24 +251,30 @@ void testDelete_Book_ShouldThrowException_WhenIdNotFound() {
293251
@Test
294252
void testDeleteBookById_Success() {
295253

296-
// Arrange:
297-
persistedBook.setDeleted(false); // ensure starting state
254+
// Arrange: ensure the test book is active
255+
persistedBook.setDeleted(false);
298256

299257
when(testBookRepository.findById(testId))
300258
.thenReturn(Optional.of(persistedBook));
301259

302-
when(testBookRepository.save(any(Book.class)))
303-
.thenReturn(persistedBook);
304-
305260
// Act: call the service method we are testing
306261
testBookService.deleteBookById(testId);
307262

308263
// Assert: the entity was marked deleted
309-
assertTrue(persistedBook.isDeleted());
264+
ArgumentCaptor<Book> bookCaptor = ArgumentCaptor.forClass(Book.class);
265+
verify(testBookRepository, times(1)).save(bookCaptor.capture());
266+
267+
// extract from the captured object (internal list)
268+
Book savedBook = bookCaptor.getValue();
269+
270+
// Assert: the service correctly marked it as deleted
271+
assertTrue(
272+
savedBook.isDeleted(),
273+
"Book passed to save() should be marked deleted"
274+
);
275+
// Assert: it is the same ID we attempted to delete
276+
assertEquals(testId, savedBook.getId());
310277

311-
// Assert: repository methods were called correctly
312-
verify(testBookRepository, times(1)).findById(testId);
313-
verify(testBookRepository, times(1)).save(persistedBook);
314278
}
315279

316280
@Test

0 commit comments

Comments
 (0)