mirror of
https://github.com/github/awesome-copilot.git
synced 2026-03-20 08:05:12 +00:00
- Introduced MockMvcTester for AssertJ-style assertions in Spring MVC testing. - Added @RestClientTest for testing REST clients with MockRestServiceServer. - Implemented RestTestClient as a modern alternative to TestRestTemplate. - Documented migration steps from Spring Boot 3.x to 4.0, including dependency and annotation changes. - Created an overview of test slices to guide testing strategies. - Included Testcontainers setup for JDBC testing with PostgreSQL and MySQL. - Enhanced @WebMvcTest documentation with examples for various HTTP methods and validation.
198 lines
4.8 KiB
Markdown
198 lines
4.8 KiB
Markdown
# @DataJpaTest
|
|
|
|
Testing JPA repositories with isolated data layer slice.
|
|
|
|
## Basic Structure
|
|
|
|
```java
|
|
@DataJpaTest
|
|
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
|
|
@Testcontainers
|
|
class OrderRepositoryTest {
|
|
|
|
@Container
|
|
@ServiceConnection
|
|
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:18");
|
|
|
|
@Autowired
|
|
private OrderRepository orderRepository;
|
|
|
|
@Autowired
|
|
private TestEntityManager entityManager;
|
|
}
|
|
```
|
|
|
|
## What Gets Loaded
|
|
|
|
- Repository beans
|
|
- EntityManager / TestEntityManager
|
|
- DataSource
|
|
- Transaction manager
|
|
- No web layer, no services, no controllers
|
|
|
|
## Testing Custom Queries
|
|
|
|
```java
|
|
@Test
|
|
void shouldFindOrdersByStatus() {
|
|
// Given - Using var for cleaner code
|
|
var pending = new Order("PENDING");
|
|
var completed = new Order("COMPLETED");
|
|
entityManager.persist(pending);
|
|
entityManager.persist(completed);
|
|
entityManager.flush();
|
|
|
|
// When
|
|
var pendingOrders = orderRepository.findByStatus("PENDING");
|
|
|
|
// Then - Using sequenced collection methods
|
|
assertThat(pendingOrders).hasSize(1);
|
|
assertThat(pendingOrders.getFirst().getStatus()).isEqualTo("PENDING");
|
|
}
|
|
```
|
|
|
|
## Testing Native Queries
|
|
|
|
```java
|
|
@Test
|
|
void shouldExecuteNativeQuery() {
|
|
entityManager.persist(new Order("PENDING", BigDecimal.valueOf(100)));
|
|
entityManager.persist(new Order("PENDING", BigDecimal.valueOf(200)));
|
|
entityManager.flush();
|
|
|
|
var total = orderRepository.calculatePendingTotal();
|
|
|
|
assertThat(total).isEqualTo(new BigDecimal("300.00"));
|
|
}
|
|
```
|
|
|
|
## Testing Pagination
|
|
|
|
```java
|
|
@Test
|
|
void shouldReturnPagedResults() {
|
|
// Insert 20 orders using IntStream
|
|
IntStream.range(0, 20).forEach(i -> {
|
|
entityManager.persist(new Order("PENDING"));
|
|
});
|
|
entityManager.flush();
|
|
|
|
var page = orderRepository.findByStatus("PENDING", PageRequest.of(0, 10));
|
|
|
|
assertThat(page.getContent()).hasSize(10);
|
|
assertThat(page.getTotalElements()).isEqualTo(20);
|
|
assertThat(page.getContent().getFirst().getStatus()).isEqualTo("PENDING");
|
|
}
|
|
```
|
|
|
|
## Testing Lazy Loading
|
|
|
|
```java
|
|
@Test
|
|
void shouldLazyLoadOrderItems() {
|
|
var order = new Order("PENDING");
|
|
order.addItem(new OrderItem("Product", 2));
|
|
entityManager.persist(order);
|
|
entityManager.flush();
|
|
entityManager.clear(); // Detach from persistence context
|
|
|
|
var found = orderRepository.findById(order.getId());
|
|
|
|
assertThat(found).isPresent();
|
|
// This will trigger lazy loading
|
|
assertThat(found.get().getItems()).hasSize(1);
|
|
assertThat(found.get().getItems().getFirst().getProduct()).isEqualTo("Product");
|
|
}
|
|
```
|
|
|
|
## Testing Cascading
|
|
|
|
```java
|
|
@Test
|
|
void shouldCascadeDelete() {
|
|
var order = new Order("PENDING");
|
|
order.addItem(new OrderItem("Product", 2));
|
|
entityManager.persist(order);
|
|
entityManager.flush();
|
|
|
|
orderRepository.delete(order);
|
|
entityManager.flush();
|
|
|
|
assertThat(entityManager.find(OrderItem.class, order.getItems().getFirst().getId()))
|
|
.isNull();
|
|
}
|
|
```
|
|
|
|
## Testing @Query Methods
|
|
|
|
```java
|
|
@Query("SELECT o FROM Order o WHERE o.createdAt > :date AND o.status = :status")
|
|
List<Order> findRecentByStatus(@Param("date") LocalDateTime date,
|
|
@Param("status") String status);
|
|
|
|
@Test
|
|
void shouldFindRecentOrders() {
|
|
var old = new Order("PENDING");
|
|
old.setCreatedAt(LocalDateTime.now().minusDays(10));
|
|
var recent = new Order("PENDING");
|
|
recent.setCreatedAt(LocalDateTime.now().minusHours(1));
|
|
|
|
entityManager.persist(old);
|
|
entityManager.persist(recent);
|
|
entityManager.flush();
|
|
|
|
var recentOrders = orderRepository.findRecentByStatus(
|
|
LocalDateTime.now().minusDays(1), "PENDING");
|
|
|
|
assertThat(recentOrders).hasSize(1);
|
|
assertThat(recentOrders.getFirst().getId()).isEqualTo(recent.getId());
|
|
}
|
|
```
|
|
|
|
## Using H2 vs Real Database
|
|
|
|
### H2 (Default - Not Recommended for Production Parity)
|
|
|
|
```java
|
|
@DataJpaTest // Uses embedded H2 by default
|
|
class OrderRepositoryH2Test {
|
|
// Fast but may miss DB-specific issues
|
|
}
|
|
```
|
|
|
|
### Testcontainers (Recommended)
|
|
|
|
```java
|
|
@DataJpaTest
|
|
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
|
|
@Testcontainers
|
|
class OrderRepositoryPostgresTest {
|
|
@Container
|
|
@ServiceConnection
|
|
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:18");
|
|
}
|
|
```
|
|
|
|
## Transaction Behavior
|
|
|
|
Tests are @Transactional by default and roll back after each test.
|
|
|
|
```java
|
|
@Test
|
|
@Rollback(false) // Don't roll back (rarely needed)
|
|
void shouldPersistData() {
|
|
orderRepository.save(new Order("PENDING"));
|
|
// Data will remain in database after test
|
|
}
|
|
```
|
|
|
|
## Key Points
|
|
|
|
1. Use TestEntityManager for setup data
|
|
2. Always flush() after persist() to trigger SQL
|
|
3. Clear() the entity manager to test lazy loading
|
|
4. Use real database (Testcontainers) for accurate results
|
|
5. Test both success and failure cases
|
|
6. Leverage Java 25 var keyword for cleaner variable declarations
|
|
7. Use sequenced collection methods (getFirst(), getLast(), reversed())
|