From java
Write JUnit 5 tests, follow TDD red-green-refactor in Java, fix failing tests, configure Maven Surefire or Gradle test execution, and choose the smallest correct test scope. Use when the user asks to write a JUnit test, follow TDD in Java, fix a failing Maven test, or needs guidance on Java test-first workflows.
npx claudepluginhub ririnto/sinon --plugin javaThis skill uses the workspace's default tool permissions.
Drive Java work through tests first, then implementation, then cleanup. The common case is writing the smallest failing JUnit 5 test, making the smallest production change, and keeping build-tool wiring separate from the test's behavioral intent.
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
Drive Java work through tests first, then implementation, then cleanup. The common case is writing the smallest failing JUnit 5 test, making the smallest production change, and keeping build-tool wiring separate from the test's behavioral intent.
@Nested when one behavior splits cleanly by scenario context.@Timeout for declarative per-test time limits and assertTimeoutPreemptively only when preemptive interruption is actually required.assertThrowsExactly when the exception type is part of the contract, and verify the returned exception message with assertEquals when the message matters.useJUnitPlatform()) only when execution setup is the actual blocker.Start with one failing JUnit 5 test:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
class RetryServiceTest {
@Test
void retriesThreeTimesBeforeFailing() {
RetryException error = assertThrowsExactly(RetryException.class, () -> service.run());
assertEquals("retry budget exhausted", error.getMessage());
}
}
Use when: starting TDD or pinning a bug boundary before changing production code.
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class ProfileServiceTest {
@Test
void returnsCachedProfileWhenPresent() {
assertEquals("user-1", service.loadProfile("user-1").id());
}
}
@CsvSource for tabular inputs:
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
class DiscountCalculatorTest {
@ParameterizedTest
@CsvSource({
"100, 10, 90",
"200, 25, 150",
"50, 0, 50"
})
void appliesDiscountCorrectly(int price, int percent, int expected) {
assertEquals(expected, calculator.apply(price, percent));
}
}
@MethodSource for complex objects:
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
class TierClassifierTest {
static Stream<Arguments> tiers() {
return Stream.of(
Arguments.of(0, "free"),
Arguments.of(999, "free"),
Arguments.of(1000, "standard"),
Arguments.of(9999, "standard")
);
}
@ParameterizedTest
@MethodSource("tiers")
void classifiesCorrectly(int points, String expectedTier) {
assertEquals(expectedTier, classifier.classify(points));
}
}
import java.io.IOException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class ClientServiceTest {
@Mock
private RemoteClient client;
@InjectMocks
private ClientService service;
@Test
void retriesAfterTransientFailure() throws IOException {
when(client.call())
.thenThrow(new IOException("temporary"))
.thenReturn("ok");
assertEquals("ok", service.run());
verify(client).call();
}
}
Argument capture:
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
class NotificationServiceTest {
@Mock
private MessageSender sender;
@Captor
private ArgumentCaptor<Message> messageCaptor;
@Test
void sendsFormattedMessage() {
service.notify("alice", "welcome");
verify(sender).send(messageCaptor.capture());
assertEquals("welcome", messageCaptor.getValue().body());
}
}
For additional Mockito features, see testing-core.md.
import java.time.Duration;
import org.junit.jupiter.api.Test;
import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.assertTrue;
class EventPublisherTest {
@Test
void publishesEventually() {
service.triggerAsyncWork();
await().atMost(Duration.ofSeconds(5)).untilAsserted(() ->
assertTrue(repository.contains("done")));
}
}
import java.math.BigDecimal;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class CartServiceTest {
@Nested
class WhenCartIsEmpty {
@Test
void returnsZeroTotal() {
assertEquals(BigDecimal.ZERO, service.total());
}
}
}
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
class FeedRefreshTest {
@Test
@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
void refreshFinishesWithinBudget() {
service.refresh();
}
}
import java.nio.file.Path;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import static org.junit.jupiter.api.Assertions.assertTrue;
class FileExporterTest {
@Test
void writesOutputFile(@TempDir Path tempDir) {
Path output = tempDir.resolve("report.csv");
exporter.exportTo(output);
assertTrue(output.toFile().exists());
}
}
Maven Surefire (unit tests, *Test.java):
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
Maven Failsafe (integration tests, *IT.java):
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
</plugin>
Gradle Groovy DSL:
test {
useJUnitPlatform()
}
Gradle Kotlin DSL:
tasks.test {
useJUnitPlatform()
}
assertTimeoutPreemptively, warn that it runs work on a separate thread and may break ThreadLocal-sensitive code such as transaction-bound framework tests.Return:
| If the blocker is... | Open... |
|---|---|
| assertion style, lifecycle hooks, parameterized JUnit 5 detail, mocking boundaries, or Awaitility-based async verification | testing-core.md |