Examples
Real-world examples and patterns
This section provides practical examples of Verifyica usage patterns.
Examples
Advanced Patterns
For more advanced patterns, see the Advanced Topics section:
Example Code Repository
Find complete, runnable examples in the Verifyica repository:
git clone https://github.com/verifyica-team/verifyica.git
cd verifyica/examples
Running Examples
Maven
cd examples
mvn clean test
IDE
Import the examples module and run test classes directly.
Example Structure
All examples follow consistent patterns:
- Clear argument suppliers - Show how to provide test data
- Lifecycle usage - Demonstrate setup/teardown
- Assertions - Show test validation
- Comments - Explain key concepts
1 - Simple Tests
Basic test examples with sequential execution
Basic test patterns using sequential argument execution.
Sequential Argument Test
Sequential execution processes one argument at a time.
Example Code
package org.verifyica.examples.simple;
import java.util.ArrayList;
import java.util.Collection;
import org.verifyica.api.Verifyica;
public class SequentialArgumentTest {
@Verifyica.ArgumentSupplier
public static Object arguments() {
Collection<String> collection = new ArrayList<>();
for (int i = 0; i < 10; i++) {
collection.add("string-" + i);
}
return collection;
}
@Verifyica.Prepare
public void prepare() {
// Called once before all arguments
System.out.println("prepare()");
}
@Verifyica.BeforeAll
public void beforeAll(String argument) {
System.out.println("beforeAll() argument [" + argument + "]");
}
@Verifyica.BeforeEach
public void beforeEach(String argument) {
System.out.println("beforeEach() argument [" + argument + "]");
}
@Verifyica.Test
public void test1(String argument) {
System.out.println("test1() argument [" + argument + "]");
}
@Verifyica.Test
public void test2(String argument) {
System.out.println("test2() argument [" + argument + "]");
}
@Verifyica.Test
public void test3(String argument) {
System.out.println("test3() argument [" + argument + "]");
}
@Verifyica.AfterEach
public void afterEach(String argument) {
System.out.println("afterEach() argument [" + argument + "]");
}
@Verifyica.AfterAll
public void afterAll(String argument) {
System.out.println("afterAll() argument [" + argument + "]");
}
@Verifyica.Conclude
public void conclude() {
// Called once after all arguments
System.out.println("conclude()");
}
}
Execution Flow
For 10 arguments, the execution flow is:
prepare() - Once- For each argument (string-0 through string-9):
beforeAll(argument)- For each test method:
beforeEach(argument)test1/2/3(argument)afterEach(argument)
afterAll(argument)
conclude() - Once
Key Features
Sequential Processing
- Arguments are processed one at a time
- No parallelism (default behavior)
- Predictable execution order
Unwrapped Arguments
- Test methods receive
String argument directly - No need for
ArgumentContext wrapper - Simpler method signatures
Complete Lifecycle
- All 7 lifecycle phases demonstrated
- Prepare and Conclude run once per test class
- BeforeAll/AfterAll run once per argument
- BeforeEach/AfterEach run for each test method
When to Use Sequential Tests
Sequential tests are appropriate when:
- Arguments have dependencies on each other
- Shared resources don’t support concurrent access
- Test execution order matters
- Debugging parallel execution issues
- Resource constraints prevent parallelism
See Also
2 - Parallel Tests
Examples demonstrating parallel argument execution
Examples using parallel execution to run multiple arguments concurrently.
Parallel Argument Test
Parallel execution processes multiple arguments simultaneously.
Example Code
package org.verifyica.examples.simple;
import java.util.ArrayList;
import java.util.Collection;
import org.verifyica.api.Verifyica;
public class ParallelArgumentTest {
@Verifyica.ArgumentSupplier(parallelism = 2)
public static Object arguments() {
Collection<String> collection = new ArrayList<>();
for (int i = 0; i < 10; i++) {
collection.add("string-" + i);
}
return collection;
}
@Verifyica.Prepare
public void prepare() {
System.out.println("prepare()");
}
@Verifyica.BeforeAll
public void beforeAll(String argument) {
System.out.println("beforeAll() argument [" + argument + "]");
}
@Verifyica.BeforeEach
public void beforeEach(String argument) {
System.out.println("beforeEach() argument [" + argument + "]");
}
@Verifyica.Test
public void test1(String argument) {
System.out.println("test1() argument [" + argument + "]");
}
@Verifyica.Test
public void test2(String argument) {
System.out.println("test2() argument [" + argument + "]");
}
@Verifyica.Test
public void test3(String argument) {
System.out.println("test3() argument [" + argument + "]");
}
@Verifyica.AfterEach
public void afterEach(String argument) {
System.out.println("afterEach() argument [" + argument + "]");
}
@Verifyica.AfterAll
public void afterAll(String argument) {
System.out.println("afterAll() argument [" + argument + "]");
}
@Verifyica.Conclude
public void conclude() {
System.out.println("conclude()");
}
}
Key Difference from Sequential
The only change from sequential execution is the parallelism parameter:
@Verifyica.ArgumentSupplier(parallelism = 2)
This allows up to 2 arguments to execute concurrently.
Execution Flow
With parallelism = 2 and 10 arguments:
prepare() - Once- Process arguments in batches of 2:
- Batch 1: string-0 and string-1 run concurrently
- Batch 2: string-2 and string-3 run concurrently
- Batch 3: string-4 and string-5 run concurrently
- Batch 4: string-6 and string-7 run concurrently
- Batch 5: string-8 and string-9 run concurrently
conclude() - Once
Thread Safety Considerations
Single Instance Model
A single test class instance is shared across all arguments:
public class ParallelArgumentTest {
// UNSAFE: Shared mutable state without synchronization
private int sharedCounter = 0; // Will cause race conditions!
@Verifyica.Test
public void test(String argument) {
sharedCounter++; // Race condition with parallel execution
}
}
Safe Patterns
Use context classes to isolate per-argument state:
public static class TestContext {
private final String data;
private int counter = 0; // Safe: isolated per argument
public TestContext(String data) {
this.data = data;
}
public void increment() {
counter++;
}
}
@Verifyica.BeforeAll
public void beforeAll(ArgumentContext argumentContext) {
String argument = argumentContext.getArgument().getPayloadAs(String.class);
TestContext context = new TestContext(argument);
argumentContext.getMap().put("testContext", context);
}
@Verifyica.Test
public void test(ArgumentContext argumentContext) {
TestContext context = (TestContext) argumentContext.getMap().get("testContext");
context.increment(); // Safe: each argument has its own TestContext
}
Parallelism Levels
ArgumentSupplier Level
Control parallelism at the argument level:
@Verifyica.ArgumentSupplier(parallelism = 4) // Up to 4 concurrent arguments
Test Method Level
Control parallelism for individual test methods:
@Verifyica.Test(parallelism = 3) // Up to 3 concurrent executions of this test
public void test(String argument) {
// Test logic
}
Unlimited Parallelism
Use Integer.MAX_VALUE for maximum parallelism:
@Verifyica.ArgumentSupplier(parallelism = Integer.MAX_VALUE)
This allows all arguments to execute concurrently (subject to available threads).
Sequential (parallelism = 1)
- 10 arguments × 3 tests × 1 second = 30 seconds total
Parallel (parallelism = 5)
- 10 arguments ÷ 5 concurrent = 2 batches
- 2 batches × 3 tests × 1 second = 6 seconds total
- 5x faster
When to Use Parallel Tests
Parallel execution is beneficial when:
- Arguments are independent
- Tests are CPU or I/O bound
- Large number of arguments to process
- Resources support concurrent access
- Faster test feedback is needed
See Also
3 - Interceptor Examples
Examples of ClassInterceptor usage
Examples demonstrating how to use ClassInterceptor to hook into test lifecycle events.
ClassInterceptor Example
Interceptors allow you to inject custom logic around test lifecycle phases.
Custom Interceptor Implementation
package org.verifyica.examples.interceptor;
import java.lang.reflect.Method;
import org.verifyica.api.ArgumentContext;
import org.verifyica.api.ClassInterceptor;
import org.verifyica.api.EngineContext;
public class CustomClassInterceptor2 implements ClassInterceptor {
@Override
public void initialize(EngineContext engineContext) throws Throwable {
System.out.println("initialize()");
}
@Override
public void preTest(ArgumentContext argumentContext, Method testMethod) {
System.out.println("preTest() test class [" +
argumentContext.getClassContext().getTestClass().getSimpleName() +
"] test method [" + testMethod.getName() + "]");
}
@Override
public void postTest(ArgumentContext argumentContext, Method testMethod, Throwable throwable)
throws Throwable {
System.out.println("postTest() test class [" +
argumentContext.getClassContext().getTestClass().getSimpleName() +
"] test method [" + testMethod.getName() + "]");
rethrow(throwable);
}
@Override
public void destroy(EngineContext engineContext) throws Throwable {
System.out.println("destroy()");
}
}
Test Class Using Interceptor
package org.verifyica.examples.interceptor;
import java.util.ArrayList;
import java.util.Collection;
import org.verifyica.api.ClassInterceptor;
import org.verifyica.api.Verifyica;
public class ClassInterceptorTest {
@Verifyica.ClassInterceptorSupplier
public static Collection<ClassInterceptor> classInterceptors() {
Collection<ClassInterceptor> collections = new ArrayList<>();
collections.add(new CustomClassInterceptor2());
return collections;
}
@Verifyica.ArgumentSupplier
public static Object arguments() {
Collection<String> collection = new ArrayList<>();
for (int i = 0; i < 10; i++) {
collection.add("string-" + i);
}
return collection;
}
@Verifyica.Prepare
public void prepare() {
System.out.println("prepare()");
}
@Verifyica.BeforeAll
public void beforeAll(String argument) {
System.out.println("beforeAll() argument [" + argument + "]");
}
@Verifyica.BeforeEach
public void beforeEach(String argument) {
System.out.println("beforeEach() argument [" + argument + "]");
}
@Verifyica.Test
public void test1(String argument) {
System.out.println("test1() argument [" + argument + "]");
}
@Verifyica.Test
public void test2(String argument) {
System.out.println("test2() argument [" + argument + "]");
}
@Verifyica.Test
public void test3(String argument) {
System.out.println("test3() argument [" + argument + "]");
}
@Verifyica.AfterEach
public void afterEach(String argument) {
System.out.println("afterEach() argument [" + argument + "]");
}
@Verifyica.AfterAll
public void afterAll(String argument) {
System.out.println("afterAll() argument [" + argument + "]");
}
@Verifyica.Conclude
public void conclude() {
System.out.println("conclude()");
}
}
Execution Flow
With the interceptor active, execution for each test method follows:
preTest() - Interceptor runs before test method- Test method executes
postTest() - Interceptor runs after test method
Output Example
For a single argument:
initialize()
prepare()
beforeAll() argument [string-0]
beforeEach() argument [string-0]
preTest() test class [ClassInterceptorTest] test method [test1]
test1() argument [string-0]
postTest() test class [ClassInterceptorTest] test method [test1]
afterEach() argument [string-0]
beforeEach() argument [string-0]
preTest() test class [ClassInterceptorTest] test method [test2]
test2() argument [string-0]
postTest() test class [ClassInterceptorTest] test method [test2]
afterEach() argument [string-0]
beforeEach() argument [string-0]
preTest() test class [ClassInterceptorTest] test method [test3]
test3() argument [string-0]
postTest() test class [ClassInterceptorTest] test method [test3]
afterEach() argument [string-0]
afterAll() argument [string-0]
conclude()
destroy()
ClassInterceptor Lifecycle Hooks
ClassInterceptor provides hooks for all lifecycle phases:
Engine-Level Hooks
void initialize(EngineContext engineContext) // Before any tests
void destroy(EngineContext engineContext) // After all tests
Class-Level Hooks
void prePrepare(ClassContext classContext, Method method)
void postPrepare(ClassContext classContext, Method method, Throwable throwable)
void preConclude(ClassContext classContext, Method method)
void postConclude(ClassContext classContext, Method method, Throwable throwable)
Argument-Level Hooks
void preBeforeAll(ArgumentContext argumentContext, Method method)
void postBeforeAll(ArgumentContext argumentContext, Method method, Throwable throwable)
void preAfterAll(ArgumentContext argumentContext, Method method)
void postAfterAll(ArgumentContext argumentContext, Method method, Throwable throwable)
Test-Level Hooks
void preBeforeEach(ArgumentContext argumentContext, Method method)
void postBeforeEach(ArgumentContext argumentContext, Method method, Throwable throwable)
void preTest(ArgumentContext argumentContext, Method method)
void postTest(ArgumentContext argumentContext, Method method, Throwable throwable)
void preAfterEach(ArgumentContext argumentContext, Method method)
void postAfterEach(ArgumentContext argumentContext, Method method, Throwable throwable)
Use Cases
Logging and Monitoring
- Track test execution timing
- Log test start/end events
- Monitor resource usage
Resource Management
- Setup shared resources before tests
- Cleanup resources after tests
- Connection pooling
Test Context Management
- Store test metadata in context maps
- Share state across lifecycle phases
- Track test execution state
Error Handling
- Capture and log exceptions
- Retry failed tests
- Skip dependent tests on failure
Metrics Collection
- Measure test duration
- Count test executions
- Track success/failure rates
Registration Methods
Per-Class Registration
Use @ClassInterceptorSupplier to register interceptors for a specific test class:
@Verifyica.ClassInterceptorSupplier
public static Collection<ClassInterceptor> classInterceptors() {
return List.of(new MyInterceptor());
}
Global Registration
Register interceptors globally via ServiceLoader in META-INF/services/org.verifyica.api.ClassInterceptor:
com.example.MyGlobalInterceptor
com.example.AnotherInterceptor
Best Practices
Selective Interception
- Use
predicate() to filter which classes the interceptor applies to - Avoid intercepting every test unnecessarily
Error Propagation
- Always call
rethrow(throwable) in post* methods to propagate test failures - Don’t swallow exceptions unintentionally
Minimal Overhead
- Keep interceptor logic lightweight
- Avoid expensive operations in hot paths
Thread Safety
- Interceptors may be called concurrently with parallel execution
- Use thread-safe data structures if maintaining state
See Also
4 - TestContainers Examples
Examples integrating Verifyica with TestContainers
Examples demonstrating how to integrate Verifyica with TestContainers for container-based testing.
Nginx Container Test
This example shows testing multiple Nginx versions in parallel using TestContainers.
Test Environment Class
The NginxTestEnvironment implements Argument<T> to provide test data:
package org.verifyica.examples.testcontainers.nginx;
import java.time.Duration;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.NginxContainer;
import org.testcontainers.utility.DockerImageName;
import org.verifyica.api.Argument;
public class NginxTestEnvironment implements Argument<NginxTestEnvironment> {
private final String dockerImageName;
private NginxContainer<?> nginxContainer;
public NginxTestEnvironment(String dockerImageName) {
this.dockerImageName = dockerImageName;
}
@Override
public String getName() {
return dockerImageName;
}
@Override
public NginxTestEnvironment getPayload() {
return this;
}
public void initialize(Network network) {
nginxContainer = new NginxContainer<>(DockerImageName.parse(dockerImageName))
.withNetwork(network)
.withStartupTimeout(Duration.ofSeconds(30));
try {
nginxContainer.start();
} catch (Exception e) {
nginxContainer.stop();
throw e;
}
}
public boolean isRunning() {
return nginxContainer.isRunning();
}
public NginxContainer<?> getNginxContainer() {
return nginxContainer;
}
public void destroy() {
if (nginxContainer != null) {
nginxContainer.stop();
nginxContainer = null;
}
}
}
Test Class
package org.verifyica.examples.testcontainers.nginx;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URLConnection;
import java.util.stream.Stream;
import org.testcontainers.containers.Network;
import org.verifyica.api.Verifyica;
import org.verifyica.api.util.CleanupExecutor;
public class NginxTest {
private final ThreadLocal<Network> networkThreadLocal = new ThreadLocal<>();
@Verifyica.ArgumentSupplier(parallelism = Integer.MAX_VALUE)
public static Stream<NginxTestEnvironment> arguments() throws IOException {
return Stream.of(
new NginxTestEnvironment("nginx:1.25"),
new NginxTestEnvironment("nginx:1.24"),
new NginxTestEnvironment("nginx:1.23")
);
}
@Verifyica.BeforeAll
public void initializeTestEnvironment(NginxTestEnvironment nginxTestEnvironment) {
System.out.println("[" + nginxTestEnvironment.getName() +
"] initialize test environment ...");
Network network = Network.newNetwork();
network.getId();
networkThreadLocal.set(network);
nginxTestEnvironment.initialize(network);
assertThat(nginxTestEnvironment.isRunning()).isTrue();
}
@Verifyica.Test
@Verifyica.Order(1)
public void testGet(NginxTestEnvironment nginxTestEnvironment) throws Throwable {
System.out.println("[" + nginxTestEnvironment.getName() +
"] testing testGet() ...");
int port = nginxTestEnvironment.getNginxContainer().getMappedPort(80);
String content = doGet("http://localhost:" + port);
assertThat(content).contains("Welcome to nginx!");
}
@Verifyica.AfterAll
public void destroyTestEnvironment(NginxTestEnvironment nginxTestEnvironment)
throws Throwable {
System.out.println("[" + nginxTestEnvironment.getName() +
"] destroy test environment ...");
new CleanupExecutor()
.addTask(nginxTestEnvironment::destroy)
.addTaskIfPresent(networkThreadLocal::get, Network::close)
.addTask(networkThreadLocal::remove)
.throwIfFailed();
}
private static String doGet(String url) throws Throwable {
StringBuilder result = new StringBuilder();
URLConnection connection = URI.create(url).toURL().openConnection();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(connection.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
result.append(line);
}
}
return result.toString();
}
}
Key Patterns
Unlimited Parallelism
@Verifyica.ArgumentSupplier(parallelism = Integer.MAX_VALUE)
This allows all Nginx versions to be tested concurrently, significantly reducing total test time.
CleanupExecutor for Resource Management
new CleanupExecutor()
.addTask(nginxTestEnvironment::destroy)
.addTaskIfPresent(networkThreadLocal::get, Network::close)
.addTask(networkThreadLocal::remove)
.throwIfFailed();
Benefits:
- LIFO Order: Resources cleaned up in reverse order (last added, first cleaned)
- Exception Handling: Continues cleanup even if one task fails
- Conditional Cleanup:
addTaskIfPresent only adds task if value exists - Failure Propagation:
throwIfFailed() throws if any cleanup failed
ThreadLocal for Per-Argument Isolation
private final ThreadLocal<Network> networkThreadLocal = new ThreadLocal<>();
Since a single test instance is shared across all parallel arguments, ThreadLocal ensures each argument has its own Network instance.
Custom Argument Types
By implementing Argument<T>, you can create rich test data objects:
public class NginxTestEnvironment implements Argument<NginxTestEnvironment> {
@Override
public String getName() {
return dockerImageName; // Display name in test reports
}
@Override
public NginxTestEnvironment getPayload() {
return this; // The environment itself is the payload
}
}
Execution Flow
For 3 Nginx versions with parallelism = Integer.MAX_VALUE:
- All 3 arguments execute concurrently:
initializeTestEnvironment() starts 3 containers in paralleltestGet() runs 3 HTTP tests in paralleldestroyTestEnvironment() cleans up 3 containers in parallel
Timeline:
Time 0s: [nginx:1.25, nginx:1.24, nginx:1.23] all start
Time 5s: [nginx:1.25, nginx:1.24, nginx:1.23] all containers ready
Time 6s: [nginx:1.25, nginx:1.24, nginx:1.23] all tests complete
Time 8s: [nginx:1.25, nginx:1.24, nginx:1.23] all cleanup complete
Total: 8 seconds
Sequential Equivalent:
Time 0s: [nginx:1.25] start
Time 5s: [nginx:1.25] ready
Time 6s: [nginx:1.25] test complete
Time 8s: [nginx:1.25] cleanup complete
Time 8s: [nginx:1.24] start
Time 13s: [nginx:1.24] ready
Time 14s: [nginx:1.24] test complete
Time 16s: [nginx:1.24] cleanup complete
Time 16s: [nginx:1.23] start
Time 21s: [nginx:1.23] ready
Time 22s: [nginx:1.23] test complete
Time 24s: [nginx:1.23] cleanup complete
Total: 24 seconds
Performance Gain: 3x faster with parallel execution
Additional TestContainers Examples
Kafka Integration
@Verifyica.ArgumentSupplier(parallelism = 2)
public static Stream<KafkaTestEnvironment> arguments() {
return Stream.of(
new KafkaTestEnvironment("confluentinc/cp-kafka:7.5.0"),
new KafkaTestEnvironment("confluentinc/cp-kafka:7.4.0")
);
}
@Verifyica.BeforeAll
public void setup(KafkaTestEnvironment env) {
env.start();
}
@Verifyica.Test
public void testProduceConsume(KafkaTestEnvironment env) {
// Produce and consume messages
}
@Verifyica.AfterAll
public void teardown(KafkaTestEnvironment env) {
env.stop();
}
MongoDB Integration
@Verifyica.ArgumentSupplier(parallelism = Integer.MAX_VALUE)
public static Stream<MongoTestEnvironment> arguments() {
return Stream.of(
new MongoTestEnvironment("mongo:7.0"),
new MongoTestEnvironment("mongo:6.0"),
new MongoTestEnvironment("mongo:5.0")
);
}
@Verifyica.BeforeAll
public void connect(MongoTestEnvironment env) {
env.startContainer();
MongoClient client = env.getClient();
// Initialize test data
}
@Verifyica.Test
public void testQuery(MongoTestEnvironment env) {
// Run queries
}
PostgreSQL Integration
@Verifyica.ArgumentSupplier(parallelism = 3)
public static Stream<PostgresTestEnvironment> arguments() {
return Stream.of(
new PostgresTestEnvironment("postgres:16"),
new PostgresTestEnvironment("postgres:15"),
new PostgresTestEnvironment("postgres:14")
);
}
@Verifyica.BeforeAll
public void setupDatabase(PostgresTestEnvironment env) {
env.start();
env.executeSql("CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT)");
}
@Verifyica.Test
public void testInsertSelect(PostgresTestEnvironment env) {
// Test database operations
}
Best Practices
Resource Isolation
Always use ThreadLocal or context classes for per-argument resources:
// UNSAFE: Shared across all arguments
private Network network; // Race condition!
// SAFE: Isolated per argument thread
private final ThreadLocal<Network> networkThreadLocal = new ThreadLocal<>();
Cleanup Ordering
Use CleanupExecutor to ensure proper cleanup order:
new CleanupExecutor()
.addTask(container::stop) // Clean up container first
.addTask(network::close) // Then network
.addTask(tempDir::delete) // Finally temp files
.throwIfFailed();
Error Handling
Always stop containers on startup failure:
try {
container.start();
} catch (Exception e) {
container.stop(); // Prevent resource leak
throw e;
}
Startup Timeouts
Set appropriate timeouts for container startup:
new NginxContainer<>(dockerImageName)
.withStartupTimeout(Duration.ofSeconds(30))
See Also