JUnit Jupiter API for writing tests - Core API module of JUnit 5 that provides annotations, assertions, and test lifecycle management
—
Thread-safe test execution with resource management, temporary directory support, and execution control annotations. This module provides capabilities for running tests concurrently while ensuring proper resource isolation and file system operations.
Configure how tests execute in relation to each other and control thread usage.
/**
* Configure parallel execution mode for test classes and methods
*/
@Execution(ExecutionMode value)
/**
* Execute test in isolation (forces sequential execution)
*/
@Isolated
/**
* Execution mode enumeration
*/
enum ExecutionMode {
/**
* Execute in same thread as parent
*/
SAME_THREAD,
/**
* Execute concurrently when parallel execution is enabled
*/
CONCURRENT
}Usage Examples:
import org.junit.jupiter.api.parallel.*;
import org.junit.jupiter.api.execution.ExecutionMode;
// Class-level parallel execution
@Execution(ExecutionMode.CONCURRENT)
class ParallelTest {
@Test
@Execution(ExecutionMode.CONCURRENT)
void concurrentTest1() {
// Runs concurrently with other concurrent tests
performIndependentCalculation();
}
@Test
@Execution(ExecutionMode.CONCURRENT)
void concurrentTest2() {
// Can run in parallel with concurrentTest1
performAnotherIndependentCalculation();
}
@Test
@Execution(ExecutionMode.SAME_THREAD)
void sequentialTest() {
// Always runs in same thread as parent
performThreadSensitiveOperation();
}
}
// Force sequential execution for entire class
@Execution(ExecutionMode.SAME_THREAD)
class SequentialTest {
@Test
void test1() {
// Always sequential due to class-level annotation
}
@Test
void test2() {
// Runs after test1 completes
}
}
// Isolation for tests that cannot run concurrently with anything
@Isolated
class IsolatedTest {
@Test
void isolatedTest() {
// No other tests run concurrently with this
modifyGlobalState();
}
}Declare dependencies on shared resources to prevent concurrent access conflicts.
/**
* Declare a resource dependency with access mode
*/
@ResourceLock(String value, ResourceAccessMode mode = ResourceAccessMode.READ_WRITE)
/**
* Multiple resource locks
*/
@ResourceLocks({
@ResourceLock(value = "resource1", mode = ResourceAccessMode.READ),
@ResourceLock(value = "resource2", mode = ResourceAccessMode.READ_WRITE)
})
/**
* Resource access modes
*/
enum ResourceAccessMode {
/**
* Read-only access - multiple readers allowed
*/
READ,
/**
* Read-write access - exclusive access required
*/
READ_WRITE
}
/**
* Programmatic resource lock provider
*/
interface ResourceLocksProvider {
java.util.Set<Lock> provideForNestedClass(Class<?> testClass);
java.util.Set<Lock> provideForMethod(Class<?> testClass, java.lang.reflect.Method testMethod);
}Usage Examples:
import org.junit.jupiter.api.parallel.*;
class ResourceLockTest {
@Test
@ResourceLock(value = "database", mode = ResourceAccessMode.READ_WRITE)
void exclusiveDatabaseTest() {
// Exclusive access to database resource
database.createTable("test_table");
database.insertData("test_data");
// No other database tests run concurrently
}
@Test
@ResourceLock(value = "database", mode = ResourceAccessMode.READ)
void readOnlyDatabaseTest1() {
// Can run concurrently with other READ mode tests
List<String> tables = database.listTables();
assertFalse(tables.isEmpty());
}
@Test
@ResourceLock(value = "database", mode = ResourceAccessMode.READ)
void readOnlyDatabaseTest2() {
// Can run concurrently with readOnlyDatabaseTest1
int count = database.getTableCount();
assertTrue(count >= 0);
}
@Test
@ResourceLocks({
@ResourceLock(value = "file.system", mode = ResourceAccessMode.READ_WRITE),
@ResourceLock(value = "network", mode = ResourceAccessMode.READ)
})
void multipleResourceTest() {
// Exclusive file system access, shared network access
File tempFile = new File("temp.txt");
tempFile.createNewFile();
NetworkService.readConfiguration(); // Read-only network access
}
}
// Built-in resource constants
class SystemResourceTest {
@Test
@ResourceLock(Resources.SYSTEM_PROPERTIES)
void systemPropertiesTest() {
// Exclusive access to system properties
String oldValue = System.getProperty("test.property");
System.setProperty("test.property", "test-value");
try {
// Test logic
} finally {
if (oldValue != null) {
System.setProperty("test.property", oldValue);
} else {
System.clearProperty("test.property");
}
}
}
@Test
@ResourceLock(Resources.SYSTEM_OUT)
void systemOutTest() {
// Exclusive access to System.out
PrintStream originalOut = System.out;
ByteArrayOutputStream capturedOutput = new ByteArrayOutputStream();
System.setOut(new PrintStream(capturedOutput));
try {
System.out.println("Test output");
assertEquals("Test output\n", capturedOutput.toString());
} finally {
System.setOut(originalOut);
}
}
}Predefined resource identifiers for common shared resources.
/**
* Predefined resource constants for common system resources
*/
class Resources {
/**
* System properties resource
*/
static final String SYSTEM_PROPERTIES = "java.lang.System.properties";
/**
* System.out resource
*/
static final String SYSTEM_OUT = "java.lang.System.out";
/**
* System.err resource
*/
static final String SYSTEM_ERR = "java.lang.System.err";
/**
* Default locale resource
*/
static final String LOCALE = "java.util.Locale.default";
}Automatic creation and cleanup of temporary directories for tests.
/**
* Inject temporary directory into test parameter or field
*/
@TempDir(
CleanupMode cleanup = CleanupMode.DEFAULT,
TempDirFactory factory = TempDirFactory.Standard.class
)
/**
* Cleanup modes for temporary directories
*/
enum CleanupMode {
/**
* Use default cleanup behavior (clean up after test completion)
*/
DEFAULT,
/**
* Always clean up temporary directory
*/
ALWAYS,
/**
* Clean up only if test succeeds
*/
ON_SUCCESS,
/**
* Never clean up temporary directory
*/
NEVER
}
/**
* Factory for creating temporary directories
*/
interface TempDirFactory extends java.io.Closeable {
/**
* Create a new temporary directory
* @param elementContext the context of the field or parameter where @TempDir is declared
* @param extensionContext the current extension context
* @return the path to the newly created temporary directory
* @throws Exception in case of failures
*/
java.nio.file.Path createTempDirectory(java.lang.reflect.AnnotatedElement elementContext, ExtensionContext extensionContext) throws Exception;
/**
* Close resources - default implementation does nothing
*/
@Override
default void close() throws java.io.IOException;
/**
* Standard implementation using Files.createTempDirectory() with "junit-" prefix
*/
class Standard implements TempDirFactory {
public static final TempDirFactory INSTANCE = new Standard();
@Override
public java.nio.file.Path createTempDirectory(java.lang.reflect.AnnotatedElement elementContext, ExtensionContext extensionContext) throws java.io.IOException;
}
}Usage Examples:
import org.junit.jupiter.api.io.*;
import java.nio.file.*;
class TempDirectoryTest {
// Field injection
@TempDir
Path tempDirectory;
// Alternative field injection with File type
@TempDir
File tempDir;
@Test
void testWithFieldInjection() throws Exception {
// Use tempDirectory field
Path testFile = tempDirectory.resolve("test.txt");
Files.write(testFile, "Hello World".getBytes());
assertTrue(Files.exists(testFile));
assertEquals("Hello World", Files.readString(testFile));
// Also works with File type
File anotherFile = new File(tempDir, "another.txt");
assertTrue(anotherFile.createNewFile());
}
@Test
void testWithParameterInjection(@TempDir Path tempDir) throws Exception {
// Parameter injection
Path configFile = tempDir.resolve("config.properties");
Properties props = new Properties();
props.setProperty("key", "value");
try (OutputStream out = Files.newOutputStream(configFile)) {
props.store(out, "Test configuration");
}
assertTrue(Files.exists(configFile));
}
@Test
void testWithCustomCleanup(@TempDir(cleanup = CleanupMode.NEVER) Path persistentDir) throws Exception {
// Directory won't be cleaned up after test
Path importantFile = persistentDir.resolve("important.data");
Files.write(importantFile, "Important data that should persist".getBytes());
System.out.println("Data saved to: " + importantFile.toAbsolutePath());
}
@Test
void testCleanupOnSuccess(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path conditionalDir) throws Exception {
// Directory cleaned up only if test passes
Path debugFile = conditionalDir.resolve("debug.log");
Files.write(debugFile, "Debug information".getBytes());
// If test fails, debug.log will remain for investigation
assertTrue(Files.exists(debugFile));
}
// Multiple temp directories
@Test
void testMultipleTempDirs(@TempDir Path inputDir, @TempDir Path outputDir) throws Exception {
// Create input file
Path input = inputDir.resolve("input.txt");
Files.write(input, "Input data".getBytes());
// Process and create output file
Path output = outputDir.resolve("output.txt");
String processed = Files.readString(input).toUpperCase();
Files.write(output, processed.getBytes());
assertEquals("INPUT DATA", Files.readString(output));
}
}
// Custom temp directory factory
class CustomTempDirectoryTest {
public static class CustomTempDirFactory implements TempDirFactory {
@Override
public java.nio.file.Path createTempDirectory(java.lang.reflect.AnnotatedElement annotatedElement, ExtensionContext extensionContext) throws Exception {
// Create temp directory in specific location with custom naming
java.nio.file.Path baseDir = java.nio.file.Paths.get(System.getProperty("user.home"), "test-temp");
java.nio.file.Files.createDirectories(baseDir);
String dirName = "test-" + extensionContext.getDisplayName().replaceAll("[^a-zA-Z0-9]", "-") + "-" + System.currentTimeMillis();
return java.nio.file.Files.createDirectory(baseDir.resolve(dirName));
}
}
@Test
void testWithCustomFactory(@TempDir(factory = CustomTempDirFactory.class) Path customTempDir) throws Exception {
// Uses custom factory for directory creation
assertTrue(customTempDir.toAbsolutePath().toString().contains("test-temp"));
Path testFile = customTempDir.resolve("custom-test.txt");
Files.write(testFile, "Custom temp directory test".getBytes());
assertTrue(Files.exists(testFile));
}
}Control test execution timeouts at method and class levels.
import java.util.concurrent.TimeUnit;
/**
* Set timeout for test execution
*/
@Timeout(
long value,
TimeUnit unit = TimeUnit.SECONDS,
ThreadMode threadMode = ThreadMode.INFERRED
)
/**
* Thread mode for timeout handling
*/
enum ThreadMode {
/**
* Infer thread mode based on execution mode
*/
INFERRED,
/**
* Interrupt test execution in same thread
*/
SAME_THREAD,
/**
* Execute test in separate thread for timeout
*/
SEPARATE_THREAD
}Usage Examples:
import org.junit.jupiter.api.Timeout;
import java.util.concurrent.TimeUnit;
class TimeoutTest {
@Test
@Timeout(5) // 5 seconds default
void fastTest() {
// Must complete within 5 seconds
performQuickOperation();
}
@Test
@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
void veryFastTest() {
// Must complete within 500 milliseconds
int result = 2 + 2;
assertEquals(4, result);
}
@Test
@Timeout(value = 30, unit = TimeUnit.SECONDS, threadMode = ThreadMode.SEPARATE_THREAD)
void longRunningTest() {
// Runs in separate thread, interrupted after 30 seconds
performLongRunningOperation();
}
@Test
@Timeout(value = 1, unit = TimeUnit.MINUTES)
void integrationTest() {
// Integration test with 1 minute timeout
performIntegrationTest();
}
}
// Class-level timeout applies to all test methods
@Timeout(10)
class TimedTestClass {
@Test
void test1() {
// Inherits 10 second timeout from class
}
@Test
@Timeout(30) // Overrides class-level timeout
void slowTest() {
// Gets 30 second timeout instead of class default
}
}Extensions can register callbacks for thread interruption events.
/**
* Called before a thread is interrupted due to timeout
*/
interface PreInterruptCallback extends Extension {
void preInterrupt(ExtensionContext context, Thread thread) throws Exception;
}Usage Examples:
// Extension to handle cleanup before thread interruption
public class CleanupBeforeInterruptExtension implements PreInterruptCallback {
@Override
public void preInterrupt(ExtensionContext context, Thread thread) throws Exception {
System.out.println("Test about to be interrupted: " + context.getDisplayName());
// Perform cleanup before interruption
cleanup();
// Set flag to allow graceful shutdown
TestState.setInterrupting(true);
}
private void cleanup() {
// Close resources, save state, etc.
ResourceManager.closeAll();
}
}
@ExtendWith(CleanupBeforeInterruptExtension.class)
class InterruptibleTest {
@Test
@Timeout(5)
void longRunningTest() {
while (!TestState.isInterrupting()) {
// Check interruption flag periodically
performWork();
if (Thread.currentThread().isInterrupted()) {
break;
}
}
}
}Parallel execution is configured through JUnit Platform configuration properties.
Usage Examples:
# Enable parallel execution
junit.jupiter.execution.parallel.enabled=true
# Execution mode strategy
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.mode.classes.default=concurrent
# Thread pool configuration
junit.jupiter.execution.parallel.config.strategy=dynamic
junit.jupiter.execution.parallel.config.dynamic.factor=1.0
# Custom thread pool
junit.jupiter.execution.parallel.config.strategy=custom
junit.jupiter.execution.parallel.config.custom.class=com.example.CustomParallelExecutionConfigurationStrategy// Programmatic configuration
@ExtendWith(ParallelConfigExtension.class)
class ConfiguredParallelTest {
// Tests with custom parallel configuration
}
public class ParallelConfigExtension implements BeforeAllCallback {
@Override
public void beforeAll(ExtensionContext context) throws Exception {
// Configure parallel execution programmatically
System.setProperty("junit.jupiter.execution.parallel.enabled", "true");
System.setProperty("junit.jupiter.execution.parallel.mode.default", "concurrent");
}
}/**
* Represents a resource lock for synchronization
*/
class Lock {
Lock(String key, ResourceAccessMode mode);
String getKey();
ResourceAccessMode getMode();
}/**
* Strategy interface for parallel execution configuration
*/
interface ParallelExecutionConfigurationStrategy {
ParallelExecutionConfiguration createConfiguration(ConfigurationParameters configurationParameters);
}
/**
* Configuration for parallel execution
*/
interface ParallelExecutionConfiguration {
int getParallelism();
int getMinimumRunnable();
int getMaxPoolSize();
int getCorePoolSize();
long getKeepAliveTime();
java.util.concurrent.TimeUnit getKeepAliveTimeUnit();
}/**
* Interface for closeable test resources that are automatically cleaned up
*/
@FunctionalInterface
interface CloseableResource {
void close() throws Exception;
}
/**
* Resource management through extension context
*/
interface Store {
/**
* Store a closeable resource that will be automatically closed
*/
void put(Object key, CloseableResource resource);
}Usage Examples:
public class DatabaseTestExtension implements BeforeEachCallback, AfterEachCallback {
private static final Namespace NAMESPACE = Namespace.create("database");
@Override
public void beforeEach(ExtensionContext context) throws Exception {
// Store closeable resource
DatabaseConnection connection = new DatabaseConnection();
context.getStore(NAMESPACE).put("connection", connection::close);
context.getStore(NAMESPACE).put("connectionObject", connection);
}
// AfterEachCallback not needed - resources auto-closed by JUnit
}Install with Tessl CLI
npx tessl i tessl/maven-org-junit-jupiter--junit-jupiter-api