java-testing
Java testing patterns: JUnit 5, Mockito, AssertJ, Testcontainers for integration tests, and coverage with JaCoCo. Core TDD methodology for plain Java projects (non-Spring). For Spring Boot, see springboot-tdd.
Best use case
java-testing is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Java testing patterns: JUnit 5, Mockito, AssertJ, Testcontainers for integration tests, and coverage with JaCoCo. Core TDD methodology for plain Java projects (non-Spring). For Spring Boot, see springboot-tdd.
Teams using java-testing should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/java-testing/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How java-testing Compares
| Feature / Agent | java-testing | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Java testing patterns: JUnit 5, Mockito, AssertJ, Testcontainers for integration tests, and coverage with JaCoCo. Core TDD methodology for plain Java projects (non-Spring). For Spring Boot, see springboot-tdd.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Java Testing
Core testing patterns for plain Java using JUnit 5, Mockito, AssertJ, and Testcontainers.
> **Note:** This skill covers plain Java (25+) without Spring Boot. For Spring Boot projects, use `springboot-tdd` which covers MockMvc, @SpringBootTest, and the full Spring test slice annotations.
## When to Activate
- Writing tests for plain Java, Jakarta EE, or Quarkus projects
- Setting up JUnit 5 + Mockito + AssertJ test stack
- Writing integration tests with Testcontainers
- Configuring JaCoCo coverage enforcement
- Applying TDD in Java without Spring context
- Diagnosing flaky or order-dependent tests caused by shared mutable state
- Adding parameterized tests to cover multiple input variants without duplicating test methods
- Enforcing 80% line coverage via JaCoCo as a build gate in CI
## Dependencies
```xml
<!-- pom.xml -->
<dependencies>
<!-- JUnit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
<!-- Mockito -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.12.0</version>
<scope>test</scope>
</dependency>
<!-- AssertJ -->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.26.3</version>
<scope>test</scope>
</dependency>
<!-- Testcontainers -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.20.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- JaCoCo coverage -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
<executions>
<execution>
<goals><goal>prepare-agent</goal></goals>
</execution>
<execution>
<id>check</id>
<goals><goal>check</goal></goals>
<configuration>
<rules>
<rule>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.80</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
```
## JUnit 5 Patterns
```java
import org.junit.jupiter.api.*;
import static org.assertj.core.api.Assertions.*;
class PriceFormatterTest {
private PriceFormatter formatter;
@BeforeEach
void setUp() {
formatter = new PriceFormatter();
}
@Test
void formatsUsdCorrectly() {
assertThat(formatter.format(1000, "USD")).isEqualTo("$10.00");
}
@Test
void throwsOnNegativeAmount() {
assertThatThrownBy(() -> formatter.format(-1, "USD"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("negative");
}
@ParameterizedTest
@CsvSource({
"100, USD, $1.00",
"100, EUR, €1.00",
"100, GBP, £1.00",
})
void formatsMultipleCurrencies(int cents, String currency, String expected) {
assertThat(formatter.format(cents, currency)).isEqualTo(expected);
}
@Nested
class WhenCurrencyIsUnknown {
@Test
void throwsUnsupportedCurrencyException() {
assertThatThrownBy(() -> formatter.format(100, "XYZ"))
.isInstanceOf(UnsupportedCurrencyException.class);
}
}
}
```
## Mockito Patterns
```java
import org.mockito.*;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock
private OrderRepository repository;
@Mock
private EmailService emailService;
@InjectMocks
private OrderService orderService;
@Test
void createsOrderAndSendsConfirmation() {
// Arrange
var request = new CreateOrderRequest("user-123", List.of("item-1"));
var savedOrder = new Order("order-456", "user-123");
when(repository.save(any())).thenReturn(savedOrder);
// Act
var result = orderService.createOrder(request);
// Assert
assertThat(result.id()).isEqualTo("order-456");
verify(emailService).sendConfirmation("user-123", "order-456");
verify(repository, times(1)).save(any(Order.class));
}
@Test
void throwsWhenUserNotFound() {
when(repository.save(any())).thenThrow(new UserNotFoundException("user-123"));
assertThatThrownBy(() -> orderService.createOrder(new CreateOrderRequest("user-123", List.of())))
.isInstanceOf(UserNotFoundException.class);
verifyNoInteractions(emailService);
}
}
```
## AssertJ Best Practices
```java
// Prefer fluent AssertJ over JUnit assertions
// BAD:
assertEquals("expected", actual);
assertTrue(list.contains("item"));
// GOOD:
assertThat(actual).isEqualTo("expected");
assertThat(list).contains("item");
// For collections
assertThat(users)
.hasSize(3)
.extracting(User::name)
.containsExactlyInAnyOrder("Alice", "Bob", "Charlie");
// For exceptions
assertThatThrownBy(() -> service.findUser("missing"))
.isInstanceOf(UserNotFoundException.class)
.hasMessageContaining("missing");
// For optionals
assertThat(optional).isPresent().hasValue("expected");
```
## Integration Tests with Testcontainers
```java
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
@Testcontainers
class UserRepositoryIT {
@Container
static final PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:16")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
private UserRepository repository;
@BeforeEach
void setUp() {
var dataSource = createDataSource(postgres.getJdbcUrl());
repository = new JdbcUserRepository(dataSource);
}
@Test
void savesAndRetrievesUser() {
var user = new User(null, "Alice", "alice@example.com");
var saved = repository.save(user);
assertThat(saved.id()).isNotNull();
var found = repository.findById(saved.id());
assertThat(found).isPresent().hasValue(saved);
}
}
```
## Coverage with JaCoCo
```bash
# Run tests with coverage
mvn test jacoco:report
# Enforce 80% minimum (fails build if below)
mvn verify
# View report
open target/site/jacoco/index.html
```
## TDD Cycle
1. **RED**: Write `@Test` method with `assertThat(...)` — it fails because class/method doesn't exist yet
2. **GREEN**: Implement the minimum code (often just return a hardcoded value) to make the test pass
3. **REFACTOR**: Replace hardcoding with real logic, add edge cases, keep tests green
4. **VERIFY**: `mvn verify` — JaCoCo enforces 80%+ coverage
## Common Pitfalls
- **Don't use `@Mock` without `@ExtendWith(MockitoExtension.class)`** — mocks won't be injected
- **Avoid `new` in test bodies for slow dependencies** — use Mockito or Testcontainers
- **Don't assert on `toString()`** — use `isEqualTo()` with proper `.equals()`/records
- **Use `@Nested` for grouping** — keeps related test cases together without long method names
- **`verify()` after `assertThat()`** — verify side effects after asserting the primary result
## Anti-Patterns
### Using JUnit `assertEquals` Instead of AssertJ
**Wrong:**
```java
import static org.junit.jupiter.api.Assertions.*;
assertEquals("expected", actual); // swapped arg order is easy to get wrong
assertTrue(users.contains(alice)); // unhelpful failure message
assertNull(result);
```
**Correct:**
```java
import static org.assertj.core.api.Assertions.*;
assertThat(actual).isEqualTo("expected");
assertThat(users).contains(alice);
assertThat(result).isNull();
```
**Why:** AssertJ produces human-readable failure messages and its fluent API prevents argument-order mistakes that silently reverse expected/actual.
---
### Mocking Without `@ExtendWith(MockitoExtension.class)`
**Wrong:**
```java
class OrderServiceTest {
@Mock
private OrderRepository repository; // never initialized — always null
@Test
void test() {
orderService.createOrder(...); // NullPointerException
}
}
```
**Correct:**
```java
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock
private OrderRepository repository;
@InjectMocks
private OrderService orderService;
// ...
}
```
**Why:** Without the extension, `@Mock` fields are never injected by Mockito and remain `null`, causing NPEs instead of meaningful test failures.
---
### Verifying Interactions Before Asserting the Primary Result
**Wrong:**
```java
@Test
void createsOrder() {
var result = orderService.createOrder(request);
verify(emailService).sendConfirmation(any()); // side effect checked first
assertThat(result.id()).isNotNull(); // primary result buried after
}
```
**Correct:**
```java
@Test
void createsOrder() {
var result = orderService.createOrder(request);
assertThat(result.id()).isNotNull(); // primary result first
verify(emailService).sendConfirmation(any()); // side effects after
}
```
**Why:** Asserting the primary result first makes test intent clear; if the main assertion fails, the verify is irrelevant and the failure message is more meaningful.
---
### Using `@BeforeAll` with Instance Fields
**Wrong:**
```java
class ReportServiceTest {
private ReportService service;
@BeforeAll // static required, but field is not static
void setUpAll() {
service = new ReportService(); // compile error or NPE
}
}
```
**Correct:**
```java
class ReportServiceTest {
private ReportService service;
@BeforeEach
void setUp() {
service = new ReportService(); // fresh instance per test
}
}
```
**Why:** `@BeforeAll` requires a `static` method (or `@TestInstance(Lifecycle.PER_CLASS)`); use `@BeforeEach` for instance setup to guarantee test isolation.
---
### Asserting on `toString()` Output
**Wrong:**
```java
assertThat(user.toString()).contains("Alice"); // breaks on any toString() change
```
**Correct:**
```java
assertThat(user.name()).isEqualTo("Alice"); // assert the actual field
// or, for records with proper equals:
assertThat(user).isEqualTo(new User("alice-1", "Alice", "alice@example.com"));
```
**Why:** `toString()` is a debugging aid, not a contract; asserting on it couples tests to formatting details rather than behaviour.Related Skills
visual-testing
Visual Regression Testing: tool comparison (Chromatic/Percy/Playwright screenshots/BackstopJS), pixel-diff vs AI-based comparison, baseline management, flakiness strategies (masks, tolerances, waitForLoadState), CI integration with GitHub Actions, and Storybook integration.
typescript-testing
TypeScript testing patterns: Vitest for unit/integration, Playwright for E2E, MSW for API mocking, Testing Library for React components. Core TDD methodology for TypeScript/JavaScript projects.
swift-testing
Swift testing patterns: Swift Testing framework (Swift 6+), XCTest for UI tests, async/await test cases, actor testing, Combine testing, and XCUITest for UI automation. TDD for Swift/SwiftUI.
swift-protocol-di-testing
Protocol-based dependency injection for testable Swift code — mock file system, network, and external APIs using focused protocols and Swift Testing.
scala-testing
Scala testing with ScalaTest, MUnit, and ScalaCheck: FunSpec/FlatSpec test structure, property-based testing with forAll, mocking with MockitoSugar, Cats Effect testing with munit-cats-effect (runTest/IOSuite), ZIO Test, Testcontainers-Scala for database integration tests, and CI integration with sbt. Use when writing or reviewing Scala tests.
rust-testing
Rust testing patterns — unit tests with mockall, integration tests with sqlx transactions, HTTP handler testing (axum), benchmarks (criterion), property tests (proptest), fuzzing, and CI with cargo-nextest.
rust-testing-advanced
Advanced Rust testing anti-patterns and corrections — cfg(test) placement, expect() over unwrap(), mockall expectation ordering, executor mixing (#[tokio::test] vs block_on), PgPool isolation with
ruby-testing
RSpec testing patterns for Ruby and Rails — factories, mocks, request specs, feature specs, VCR, and SimpleCov coverage.
r-testing
R testing patterns: testthat 3e with expect_* assertions, snapshot testing, mocking with mockery and httptest2, covr code coverage, lintr static analysis, property-based testing with hedgehog, testing Shiny apps with shinytest2. Use when writing or reviewing R tests.
python-testing
Python testing strategies using pytest, TDD methodology, fixtures, mocking, and parametrization. Core testing fundamentals.
python-testing-advanced
Advanced Python testing — async testing with pytest-asyncio, exception/side-effect testing, test organization, common patterns (API, database, class methods), pytest configuration, and CLI reference. Extends python-testing.
php-testing
PHP testing patterns: PHPUnit 11 with mocks and data providers, Pest v3 with expectations and datasets, Laravel feature/HTTP tests with RefreshDatabase, Symfony WebTestCase, PHPStan static analysis, Infection mutation testing. Use when writing or reviewing PHP tests.