Install
$ agentstack add skill-versoxbt-claude-initial-setup-junit-testing ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
JUnit 5 Testing Patterns
Patterns for writing effective unit, integration, and slice tests in Java.
When to Use
- User is writing JUnit 5 tests
- User needs to mock dependencies with Mockito
- User asks about Spring Boot test slices (@WebMvcTest, @DataJpaTest)
- User wants integration tests with real databases (Testcontainers)
- User needs parameterized tests for multiple inputs
Core Patterns
JUnit 5 Annotations and Lifecycle
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
class UserServiceTest {
private UserService userService;
private UserRepository userRepository;
@BeforeEach
void setUp() {
userRepository = new InMemoryUserRepository();
userService = new UserService(userRepository);
}
@Test
@DisplayName("creates a user with valid input")
void createsUserWithValidInput() {
User result = userService.createUser(new CreateUserRequest("Alice", "alice@example.com"));
assertAll(
() -> assertNotNull(result.getId()),
() -> assertEquals("Alice", result.getName()),
() -> assertEquals("alice@example.com", result.getEmail())
);
}
@Test
@DisplayName("throws when email is already taken")
void throwsWhenEmailTaken() {
userRepository.save(new User("Bob", "bob@example.com"));
assertThrows(ConflictException.class,
() -> userService.createUser(new CreateUserRequest("Bob2", "bob@example.com")));
}
@Nested
@DisplayName("when user exists")
class WhenUserExists {
private User existingUser;
@BeforeEach
void setUp() {
existingUser = userRepository.save(new User("Alice", "alice@example.com"));
}
@Test
void findsById() {
Optional found = userService.findById(existingUser.getId());
assertTrue(found.isPresent());
assertEquals("Alice", found.get().getName());
}
}
}
Mockito -- Mocking Dependencies
Isolate the unit under test by mocking its collaborators.
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.*;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock private OrderRepository orderRepository;
@Mock private PaymentGateway paymentGateway;
@InjectMocks private OrderService orderService;
@Test
@DisplayName("places order and charges payment")
void placesOrderAndChargesPayment() {
var order = new Order("product-1", 2, 29_99L);
when(orderRepository.save(any(Order.class))).thenReturn(order);
when(paymentGateway.charge(anyLong())).thenReturn(new PaymentResult(true, "txn-123"));
assertNotNull(orderService.placeOrder(order));
verify(paymentGateway).charge(29_99L);
verify(orderRepository).save(order);
}
@Test
@DisplayName("rolls back order when payment fails")
void rollsBackWhenPaymentFails() {
when(paymentGateway.charge(anyLong())).thenReturn(new PaymentResult(false, null));
assertThrows(PaymentFailedException.class,
() -> orderService.placeOrder(new Order("product-1", 1, 50_00L)));
verify(orderRepository, never()).save(any());
}
}
@WebMvcTest -- Controller Slice Tests
Test controllers in isolation without starting the full application context. Only the web layer is loaded.
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired private MockMvc mockMvc;
@MockBean private UserService userService;
@Test
@DisplayName("GET /api/v1/users/{id} returns user")
void getUser() throws Exception {
when(userService.findById("1"))
.thenReturn(Optional.of(new UserResponse("1", "Alice", "alice@example.com")));
mockMvc.perform(get("/api/v1/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.name").value("Alice"));
}
@Test
@DisplayName("POST /api/v1/users validates request body")
void validateCreateUser() throws Exception {
mockMvc.perform(post("/api/v1/users")
.contentType("application/json")
.content("{\"name\":\"\",\"email\":\"not-an-email\"}"))
.andExpect(status().isBadRequest());
}
}
Testcontainers -- Integration Tests with Real Databases
Spin up real database containers for integration tests. Tests are reliable and match production behavior.
@SpringBootTest
@Testcontainers
class UserRepositoryIntegrationTest {
@Container
static PostgreSQLContainer postgres = new PostgreSQLContainer<>("postgres:16-alpine")
.withDatabaseName("testdb").withUsername("test").withPassword("test");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
@Autowired private UserRepository userRepository;
@Test
@DisplayName("persists and retrieves a user")
void persistsAndRetrieves() {
User saved = userRepository.save(new User("Alice", "alice@example.com"));
Optional found = userRepository.findById(saved.getId());
assertTrue(found.isPresent());
assertEquals("Alice", found.get().getName());
}
}
Parameterized Tests
Run the same test logic with different inputs. Reduces duplication.
class EmailValidatorTest {
private final EmailValidator validator = new EmailValidator();
@ParameterizedTest
@CsvSource({"alice@example.com, true", "bob@test.org, true", "invalid, false", "@no-local.com, false"})
void validatesEmail(String email, boolean expected) {
assertEquals(expected, validator.isValid(email));
}
@ParameterizedTest
@NullAndEmptySource
@ValueSource(strings = {" ", "\t"})
void rejectsBlankEmails(String email) {
assertFalse(validator.isValid(email));
}
@ParameterizedTest
@MethodSource("validEmailProvider")
void acceptsValidEmails(String email) { assertTrue(validator.isValid(email)); }
static Stream validEmailProvider() {
return Stream.of("a@b.com", "user+tag@domain.co", "first.last@sub.domain.org");
}
}
Anti-Patterns
- Testing implementation details -- Verifying private method calls or internal state. Test behavior (inputs and outputs), not how the code achieves the result.
- Overusing @SpringBootTest -- Loads the entire application context. Slow. Use slice annotations (@WebMvcTest, @DataJpaTest) to load only what you need.
- Not using @ExtendWith(MockitoExtension.class) -- Forgetting this means @Mock and @InjectMocks annotations are silently ignored, leading to NullPointerException.
- Sharing mutable state between tests -- Tests must be independent. Use @BeforeEach to reset state. Never rely on test execution order.
- Mocking everything including the class under test -- Only mock collaborators. The class under test should use its real implementation.
Quick Reference
| Category | Key APIs | |----------|----------| | Lifecycle | @BeforeEach, @AfterEach, @BeforeAll, @AfterAll, @Nested | | Assertions | assertEquals, assertTrue, assertThrows, assertAll, assertTimeout | | Mockito | when().thenReturn(), verify(), any(), @Mock, @InjectMocks | | Slices | @WebMvcTest (controllers), @DataJpaTest (JPA), @SpringBootTest (full) | | Parameterized | @CsvSource, @ValueSource, @MethodSource, @NullAndEmptySource |
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: VersoXBT
- Source: VersoXBT/claude-initial-setup
- License: MIT
- Homepage: https://github.com/VersoXBT/claude-initial-setup#installation
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.