Skip to content

Commit 5b6c1a6

Browse files
committed
refactor(tests): address code review findings and improve clarity
Documentation updates: - Update test naming convention to givenX_whenY_thenZ BDD pattern - Update PlayersRepository JavaDoc to reflect derived query methods - Update assertion examples to BDD then() style Code improvements: - Remove redundant @query annotation from findByLeagueContainingIgnoreCase - Remove unused PlayersRepository mock from controller tests - Fix Integer comparisons to use .equals() instead of == operator - Remove redundant size assertions covered by recursive comparison - Replace magic string "Messi" with explicit variable - Rename misleading "expected" variables to descriptive names (nonExistentId, leagueName, nonExistentSquadNumber, etc.) - Add content assertion to retrieveAll service test
1 parent b844951 commit 5b6c1a6

File tree

7 files changed

+39
-49
lines changed

7 files changed

+39
-49
lines changed

.coderabbit.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ reviews:
8484
- Test classes should use JUnit 5 (Jupiter)
8585
- Verify proper Spring test annotations (@WebMvcTest, @DataJpaTest, etc.)
8686
- Check use of @MockitoBean (Spring Boot 4.0 style)
87-
- Ensure test naming follows method_scenario_outcome pattern (concise)
87+
- Ensure test naming follows givenX_whenY_thenZ BDD pattern
8888
- Validate BDD semantics in JavaDoc comments (Given/When/Then)
8989
- Validate use of AssertJ for fluent assertions
9090
- Check @DisplayName for readable test descriptions

.github/copilot-instructions.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ http://localhost:9001/actuator/health # Health check
4343
- **Validation**: Bean Validation annotations in DTOs (`@NotNull`, `@Min`, etc.)
4444
- **Caching**: Spring `@Cacheable` on service layer (1-hour TTL)
4545
- **DTO Pattern**: ModelMapper for entity ↔ DTO transformations
46-
- **Repository**: Spring Data JPA with derived queries and custom JPQL
46+
- **Repository**: Spring Data JPA with derived queries (prefer over custom JPQL)
4747

4848
## Code Conventions
4949

@@ -67,21 +67,21 @@ http://localhost:9001/actuator/health # Health check
6767
## Testing
6868

6969
- **Structure**: `*Tests.java` in `src/test/java/` (mirrors main package structure)
70-
- **Naming Pattern**: `method_scenario_outcome`
71-
- `method`: Method under test (e.g., `post`, `findById`, `create`)
72-
- `scenario`: Context (e.g., `playerExists`, `invalidData`, `noMatches`)
73-
- `outcome`: Expected result (e.g., `returnsPlayer`, `returnsConflict`, `returnsEmpty`)
70+
- **Naming Pattern**: `givenX_whenY_thenZ` (BDD Given-When-Then)
71+
- `given`: Preconditions/context (e.g., `givenPlayerExists`, `givenInvalidData`, `givenNoMatches`)
72+
- `when`: Action being tested (e.g., `whenPost`, `whenFindById`, `whenCreate`)
73+
- `then`: Expected outcome (e.g., `thenReturnsPlayer`, `thenReturnsConflict`, `thenReturnsEmpty`)
7474
- **Examples**:
7575

7676
```java
7777
// Controller
78-
void post_squadNumberExists_returnsConflict()
78+
void givenSquadNumberExists_whenPost_thenReturnsConflict()
7979

8080
// Service
81-
void create_noConflict_returnsPlayerDTO()
81+
void givenNoConflict_whenCreate_thenReturnsPlayerDTO()
8282

8383
// Repository
84-
void findById_playerExists_returnsPlayer()
84+
void givenPlayerExists_whenFindById_thenReturnsPlayer()
8585
```
8686

8787
- **JavaDoc**: BDD Given/When/Then structure in test comments
@@ -97,7 +97,7 @@ http://localhost:9001/actuator/health # Health check
9797
```
9898

9999
- **Annotations**: `@SpringBootTest`, `@AutoConfigureMockMvc`, `@Test`
100-
- **Assertions**: AssertJ (fluent, e.g., `assertThat(result).isNotNull()`)
100+
- **Assertions**: AssertJ BDD style (e.g., `then(result).isNotNull()`)
101101
- **Mocking**: Mockito for service layer tests
102102
- **Database**: Tests use in-memory SQLite (auto-cleared after each test)
103103
- **Coverage**: Target high coverage (JaCoCo reports in `target/site/jacoco/`)

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -243,10 +243,10 @@ open target/site/jacoco/index.html
243243
- **Test Database** - SQLite in-memory (jdbc:sqlite::memory:) for fast, isolated test execution
244244
- **Mocking** - Mockito with `@MockitoBean` for dependency mocking
245245
- **Assertions** - AssertJ fluent assertions
246-
- **Naming Convention** - `method_scenario_outcome` pattern:
247-
- `getAll_playersExist_returnsOkWithAllPlayers()`
248-
- `post_squadNumberExists_returnsConflict()`
249-
- `findById_playerExists_returnsPlayer()`
246+
- **Naming Convention** - `givenX_whenY_thenZ` BDD pattern:
247+
- `givenPlayersExist_whenGetAll_thenReturnsOkWithAllPlayers()`
248+
- `givenSquadNumberExists_whenPost_thenReturnsConflict()`
249+
- `givenPlayerExists_whenFindById_thenReturnsPlayer()`
250250

251251
**Coverage Targets:**
252252

src/main/java/ar/com/nanotaboada/java/samples/spring/boot/repositories/PlayersRepository.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
import java.util.Optional;
55

66
import org.springframework.data.jpa.repository.JpaRepository;
7-
import org.springframework.data.jpa.repository.Query;
8-
import org.springframework.data.repository.query.Param;
97
import org.springframework.stereotype.Repository;
108

119
import ar.com.nanotaboada.java.samples.spring.boot.models.Player;
@@ -26,8 +24,8 @@
2624
*
2725
* <h3>Query Strategies:</h3>
2826
* <ul>
29-
* <li><b>@Query:</b> Explicit JPQL for complex searches (findByLeagueContainingIgnoreCase)</li>
30-
* <li><b>Method Names:</b> Spring Data derives queries from method names (Query Creation)</li>
27+
* <li><b>Derived Queries:</b> Spring Data derives queries from method names (findBySquadNumber,
28+
* findByLeagueContainingIgnoreCase)</li>
3129
* </ul>
3230
*
3331
* @see Player
@@ -57,14 +55,13 @@ public interface PlayersRepository extends JpaRepository<Player, Long> {
5755
/**
5856
* Finds players by league name using case-insensitive wildcard matching.
5957
* <p>
60-
* This method uses a custom JPQL query with LIKE operator for partial matches.
58+
* This method uses Spring Data's derived query mechanism to perform partial matching.
6159
* For example, searching for "Premier" will match "Premier League".
6260
* </p>
6361
*
6462
* @param league the league name to search for (partial matches allowed)
6563
* @return a list of players whose league name contains the search term (empty
6664
* list if none found)
6765
*/
68-
@Query("SELECT p FROM Player p WHERE LOWER(p.league) LIKE LOWER(CONCAT('%', :league, '%'))")
69-
List<Player> findByLeagueContainingIgnoreCase(@Param("league") String league);
66+
List<Player> findByLeagueContainingIgnoreCase(String league);
7067
}

src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/controllers/PlayersControllerTests.java

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232

3333
import ar.com.nanotaboada.java.samples.spring.boot.controllers.PlayersController;
3434
import ar.com.nanotaboada.java.samples.spring.boot.models.PlayerDTO;
35-
import ar.com.nanotaboada.java.samples.spring.boot.repositories.PlayersRepository;
3635
import ar.com.nanotaboada.java.samples.spring.boot.services.PlayersService;
3736
import ar.com.nanotaboada.java.samples.spring.boot.test.PlayerDTOFakes;
3837

@@ -50,9 +49,6 @@ class PlayersControllerTests {
5049
@MockitoBean
5150
private PlayersService playersServiceMock;
5251

53-
@MockitoBean
54-
private PlayersRepository playersRepositoryMock;
55-
5652
@Autowired
5753
private ObjectMapper objectMapper;
5854

@@ -188,7 +184,6 @@ void givenPlayersExist_whenGetAll_thenReturnsOkWithAllPlayers()
188184
then(response.getContentType()).contains("application/json");
189185
verify(playersServiceMock, times(1)).retrieveAll();
190186
then(response.getStatus()).isEqualTo(HttpStatus.OK.value());
191-
then(actual).hasSize(26);
192187
then(actual).usingRecursiveComparison().isEqualTo(expected);
193188
}
194189

@@ -257,7 +252,7 @@ void givenPlayerExists_whenGetBySquadNumber_thenReturnsOk()
257252
// Given
258253
Integer squadNumber = 10;
259254
PlayerDTO expected = PlayerDTOFakes.createAll().stream()
260-
.filter(player -> player.getSquadNumber() == squadNumber)
255+
.filter(player -> squadNumber.equals(player.getSquadNumber()))
261256
.findFirst()
262257
.orElseThrow();
263258
Mockito
@@ -335,7 +330,6 @@ void givenPlayersExist_whenSearchByLeague_thenReturnsOk()
335330
then(response.getContentType()).contains("application/json");
336331
verify(playersServiceMock, times(1)).searchByLeague(any());
337332
then(response.getStatus()).isEqualTo(HttpStatus.OK.value());
338-
then(actual).hasSize(7);
339333
then(actual).usingRecursiveComparison().isEqualTo(expected);
340334
}
341335

src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/repositories/PlayersRepositoryTests.java

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -50,27 +50,27 @@ void givenPlayerExists_whenFindById_thenReturnsPlayer() {
5050
@Test
5151
void givenPlayerDoesNotExist_whenFindById_thenReturnsEmpty() {
5252
// Given
53-
Long expected = 999L;
53+
Long nonExistentId = 999L;
5454
// When
55-
Optional<Player> actual = repository.findById(expected);
55+
Optional<Player> actual = repository.findById(nonExistentId);
5656
// Then
5757
then(actual).isEmpty();
5858
}
5959

6060
/**
61-
* Given players exist in a specific league
61+
* Given players exist in a specific league (pre-seeded from dml.sql)
6262
* When searching by league name
6363
* Then a list of matching players is returned
6464
*/
6565
@Test
6666
void givenPlayersExist_whenFindByLeague_thenReturnsList() {
6767
// Given
68-
String expected = "Premier";
68+
String leagueName = "Premier";
6969
// When
70-
List<Player> actual = repository.findByLeagueContainingIgnoreCase(expected);
70+
List<Player> actual = repository.findByLeagueContainingIgnoreCase(leagueName);
7171
// Then
7272
then(actual).isNotEmpty()
73-
.allMatch(player -> player.getLeague().toLowerCase().contains(expected.toLowerCase()));
73+
.allMatch(player -> player.getLeague().toLowerCase().contains(leagueName.toLowerCase()));
7474
}
7575

7676
/**
@@ -81,30 +81,29 @@ void givenPlayersExist_whenFindByLeague_thenReturnsList() {
8181
@Test
8282
void givenNoPlayersExist_whenFindByLeague_thenReturnsEmptyList() {
8383
// Given
84-
String expected = "Expected League";
84+
String nonExistentLeague = "Nonexistent League";
8585
// When
86-
List<Player> actual = repository.findByLeagueContainingIgnoreCase(expected);
86+
List<Player> actual = repository.findByLeagueContainingIgnoreCase(nonExistentLeague);
8787
// Then
8888
then(actual).isEmpty();
8989
}
9090

9191
/**
92-
* Given a player with a specific squad number exists in the database
92+
* Given a player with a specific squad number exists (pre-seeded from dml.sql)
9393
* When querying by that squad number
9494
* Then the player record is returned
9595
*/
9696
@Test
9797
void givenPlayerExists_whenFindBySquadNumber_thenReturnsPlayer() {
9898
// Given
99-
Player expected = PlayerFakes.createAll().stream()
100-
.filter(p -> p.getId().equals(10L))
101-
.findFirst()
102-
.orElseThrow();
99+
Integer messiSquadNumber = 10; // Pre-seeded from dml.sql
100+
String expectedLastName = "Messi";
103101
// When
104-
Optional<Player> actual = repository.findBySquadNumber(expected.getSquadNumber());
102+
Optional<Player> actual = repository.findBySquadNumber(messiSquadNumber);
105103
// Then
106104
then(actual).isPresent();
107-
then(actual.get()).usingRecursiveComparison().isEqualTo(expected);
105+
then(actual.get().getSquadNumber()).isEqualTo(messiSquadNumber);
106+
then(actual.get().getLastName()).isEqualTo(expectedLastName);
108107
}
109108

110109
/**
@@ -115,9 +114,9 @@ void givenPlayerExists_whenFindBySquadNumber_thenReturnsPlayer() {
115114
@Test
116115
void givenPlayerDoesNotExist_whenFindBySquadNumber_thenReturnsEmpty() {
117116
// Given
118-
Integer expected = 99;
117+
Integer nonExistentSquadNumber = 99;
119118
// When
120-
Optional<Player> actual = repository.findBySquadNumber(expected);
119+
Optional<Player> actual = repository.findBySquadNumber(nonExistentSquadNumber);
121120
// Then
122121
then(actual).isEmpty();
123122
}

src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/services/PlayersServiceTests.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ void givenPlayerAlreadyExists_whenCreate_thenReturnsNull() {
8989
PlayerDTO dto = PlayerDTOFakes.createOneValid();
9090
Integer expectedSquadNumber = 10;
9191
Player existingPlayer = PlayerFakes.createAll().stream()
92-
.filter(player -> player.getSquadNumber() == expectedSquadNumber)
92+
.filter(player -> expectedSquadNumber.equals(player.getSquadNumber()))
9393
.findFirst()
9494
.orElseThrow();
9595
Mockito
@@ -159,7 +159,7 @@ void givenAllPlayersExist_whenRetrieveAll_thenReturns26Players() {
159159
List<PlayerDTO> actual = playersService.retrieveAll();
160160
// Then
161161
verify(playersRepositoryMock, times(1)).findAll();
162-
then(actual).hasSize(26);
162+
then(actual).usingRecursiveComparison().isEqualTo(dtos);
163163
}
164164

165165
/**
@@ -216,11 +216,11 @@ void givenPlayerExists_whenRetrieveBySquadNumber_thenReturnsPlayerDTO() {
216216
// Given
217217
Integer squadNumber = 10;
218218
Player entity = PlayerFakes.createAll().stream()
219-
.filter(player -> player.getSquadNumber() == squadNumber)
219+
.filter(player -> squadNumber.equals(player.getSquadNumber()))
220220
.findFirst()
221221
.orElseThrow();
222222
PlayerDTO expected = PlayerDTOFakes.createAll().stream()
223-
.filter(player -> player.getSquadNumber() == squadNumber)
223+
.filter(player -> squadNumber.equals(player.getSquadNumber()))
224224
.findFirst()
225225
.orElseThrow();
226226
Mockito

0 commit comments

Comments
 (0)