This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

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:

  1. Clear argument suppliers - Show how to provide test data
  2. Lifecycle usage - Demonstrate setup/teardown
  3. Assertions - Show test validation
  4. 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:

  1. prepare() - Once
  2. For each argument (string-0 through string-9):
    • beforeAll(argument)
    • For each test method:
      • beforeEach(argument)
      • test1/2/3(argument)
      • afterEach(argument)
    • afterAll(argument)
  3. 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:

  1. prepare() - Once
  2. 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
  3. 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).

Performance Comparison

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:

  1. preTest() - Interceptor runs before test method
  2. Test method executes
  3. 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:

  1. All 3 arguments execute concurrently:
    • initializeTestEnvironment() starts 3 containers in parallel
    • testGet() runs 3 HTTP tests in parallel
    • destroyTestEnvironment() 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