1. Abstract

RESTEasy JUnit Extension is a JUnit extension that provides seamless integration for testing Jakarta REST applications using SeBootstrap. It manages the Jakarta REST server lifecycle, provides resource injection, and offers a clean API for writing REST API tests.

2. Quick Start

2.1. Maven Dependency

Add the extension to your test dependencies:

<dependency>
    <groupId>dev.resteasy.junit</groupId>
    <artifactId>resteasy-junit-extension</artifactId>
    <version>{project.version}</version>
    <scope>test</scope>
</dependency>

<!-- Jakarta REST implementation (e.g., RESTEasy) -->
<dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>resteasy-undertow-cdi</artifactId>
    <version>${version.resteasy}</version>
    <scope>test</scope>
</dependency>

2.2. Basic Example

2.2.1. Simple Resource-Based Test

For simple tests, you can specify resource classes directly:

@RestBootstrap(HelloResource.class)
public class HelloResourceTest {

    @RestResource
    private Client client;

    @RestResource
    private URI baseUri;

    @Test
    public void testHello() {
        Response response = client.target(baseUri)
            .path("/hello")
            .request()
            .get();

        assertEquals(200, response.getStatus());
        assertEquals("Hello, World!", response.readEntity(String.class));
    }
}

2.2.2. Application-Based Test

For more complex scenarios, use a custom Application class:

@RestBootstrap(MyApplication.class)
public class HelloResourceTest {

    @RestResource
    private Client client;

    @RestResource
    private URI baseUri;

    @Test
    public void testHello() {
        Response response = client.target(baseUri)
            .path("/hello")
            .request()
            .get();

        assertEquals(200, response.getStatus());
        assertEquals("Hello, World!", response.readEntity(String.class));
    }
}

That’s it! The extension automatically:

  • Starts a SeBootstrap instance with your resources or Application

  • Injects a configured REST client

  • Provides the server’s base URI

  • Shuts everything down after tests complete

3. Core Concepts

3.1. @RestBootstrap Annotation

The @RestBootstrap annotation marks a test class to start a Jakarta REST SeBootstrap instance.

There are two ways to specify what to bootstrap:

3.1.1. Option 1: Resource Classes (Simple Cases)

For simple tests, specify Jakarta REST resource classes directly:

@RestBootstrap({UserResource.class, OrderResource.class})
public class MyTest {
    // Tests run with a synthetic Application containing these resources
}

This is ideal when you only need to test specific resources without custom Application configuration.

3.1.2. Option 2: Application Class (Advanced Cases)

For more control, specify a custom Application class:

@RestBootstrap(application = MyApplication.class)
public class MyTest {
    // Tests run with MyApplication
}

Use this when you need:

  • Custom @ApplicationPath configuration

  • Provider registration (filters, interceptors, exception mappers)

  • Custom application properties

  • Programmatic configuration in the Application class

3.1.3. Mutual Exclusivity

Important: Exactly one of value() or application() must be specified. Specifying both or neither will result in an error.

Attributes:

  • application - The Jakarta REST Application class to bootstrap (mutually exclusive with value)

  • value - Jakarta REST resource classes to bootstrap (mutually exclusive with application)

  • configFactory - Optional ConfigurationProvider for custom server configuration

  • sslClientAuthentication - SSL client authentication mode when using @SelfSignedCert (default: OPTIONAL). See SSL/TLS Testing.

  • timeout - Startup/shutdown timeout (default: 60 seconds)

  • timeoutUnit - Timeout time unit (default: SECONDS)

3.2. Resource Injection

The extension can inject several types into your tests:

Type

Description

Injection Points

jakarta.ws.rs.client.Client

REST client for making HTTP requests

Fields, Parameters

jakarta.ws.rs.client.WebTarget

WebTarget, optionally qualified with @RequestPath

Fields, Parameters

java.net.URI

Server base URI, optionally qualified with @RequestPath

Fields, Parameters

jakarta.ws.rs.core.UriBuilder

URI builder for constructing URIs (mutable, always fresh)

Parameters only

jakarta.ws.rs.SeBootstrap.Configuration

The SeBootstrap configuration

Fields, Parameters

dev.resteasy.junit.extension.api.SelfSignedCertificate

Self-signed certificate artifacts (requires @SelfSignedCert on class, @SslCert on field)

Fields, Parameters

UriBuilder is only available for method/constructor parameters and does not require the @RestResource annotation. Due to its mutable nature, a fresh instance is created for each parameter.

Injection Points:

  • Static fields

  • Instance fields

  • Constructor parameters

  • Method parameters (@BeforeAll, @BeforeEach, @AfterEach, @AfterAll, @Test)

@RestBootstrap(MyApp.class)
public class InjectionExamplesTest {

    // Static field injection
    @RestResource
    private static Client staticClient;

    // Instance field injection
    @RestResource
    private URI baseUri;

    // Constructor injection
    public InjectionExamplesTest(@RestResource Client client) {
        // ...
    }

    // Lifecycle method injection
    @BeforeEach
    public void setUp(@RestResource WebTarget target) {
        // ...
    }

    // Test method injection
    @Test
    public void testSomething(@RestResource Client client,
                              @RestResource URI uri,
                              UriBuilder builder) {  // No @RestResource needed
        // ...
    }
}

4. Advanced Features

4.1. Path Qualifiers with @RequestPath

Use @RequestPath to inject URIs or WebTargets pointing to specific paths:

@RestBootstrap(MyApp.class)
public class PathQualifiersTest {

    @RestResource
    @RequestPath("/users")
    private WebTarget usersTarget;

    @RestResource
    @RequestPath("/orders")
    private URI ordersUri;

    @Test
    public void testUsers() {
        // usersTarget already points to /users
        Response response = usersTarget.request().get();
        assertEquals(200, response.getStatus());
    }

    @Test
    public void testOrders(@RestResource @RequestPath("/orders/123") URI orderUri) {
        // orderUri points to /orders/123
        Response response = client.target(orderUri).request().get();
        assertEquals(200, response.getStatus());
    }
}

Path Formats:

  • Both /path and path work (leading slash is optional)

  • Nested paths work: /api/v1/users

4.2. Custom Client Configuration

Use @RestClientConfig to provide custom client configuration:

public class CustomTimeoutProvider implements RestClientBuilderProvider {
    @Override
    public ClientBuilder getClientBuilder() {
        return ClientBuilder.newBuilder()
            .connectTimeout(5, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS);
    }
}

@RestBootstrap(MyApp.class)
public class CustomClientTest {

    @RestResource
    @RestClientConfig(CustomTimeoutProvider.class)
    private Client customClient;

    @RestResource
    private Client defaultClient;  // Uses default configuration
}

4.2.1. Registering Providers Inline

For simple cases — registering a filter, interceptor, or message body reader/writer — you can list the provider classes directly on the annotation instead of implementing a RestClientBuilderProvider:

@RestBootstrap(MyApp.class)
public class ProviderTest {

    @RestResource
    @RestClientConfig(providers = { LoggingFilter.class, AuthFilter.class })
    private Client client;
}

Each provider is registered via ClientBuilder.register(Class), so it must have a public, no-argument constructor. A provider that requires constructor arguments or additional setup should be registered through a RestClientBuilderProvider instead.

Providers listed here are registered after the value() provider (if one is specified) has configured the builder, so a shared configuration provider can be combined with one or more providers specified at the injection point:

@RestResource
@RestClientConfig(value = CustomTimeoutProvider.class, providers = LoggingFilter.class)
private Client client;

4.3. Custom Server Configuration

4.3.1. Using Configuration Parameters

For simple configuration overrides, use JUnit configuration parameters (system properties or junit-platform.properties):

# Via system properties (useful for CI/avoiding port conflicts)
mvn test -Ddev.resteasy.junit.extension.port=8085
mvn test -Ddev.resteasy.junit.extension.host=0.0.0.0
# Or via junit-platform.properties
dev.resteasy.junit.extension.protocol=http
dev.resteasy.junit.extension.host=localhost
dev.resteasy.junit.extension.port=8080
dev.resteasy.junit.extension.root-path=/api

Supported Parameters:

  • dev.resteasy.junit.extension.protocol - Protocol (http/https)

  • dev.resteasy.junit.extension.host - Server host

  • dev.resteasy.junit.extension.port - Server port

  • dev.resteasy.junit.extension.root-path - Application root path

Note: Configuration parameters are only used when no explicit configFactory is specified on @RestBootstrap.

4.3.2. Using ConfigurationProvider

For more complex configuration, implement ConfigurationProvider:

public class CustomPortProvider implements ConfigurationProvider {
    @Override
    public SeBootstrap.Configuration getConfiguration(ExtensionContext context) {
        return SeBootstrap.Configuration.builder()
            .port(9090)
            .host("localhost")
            .build();
    }
}

@RestBootstrap(value = MyApp.class, configFactory = CustomPortProvider.class)
public class CustomPortTest {
    // Tests run on port 9090
    // Configuration parameters are ignored when configFactory is specified
}

4.4. SSL/TLS Testing

The extension provides built-in support for SSL/TLS testing with self-signed certificates. No external certificate setup or keystore configuration is required.

4.4.1. Quick Start

Add @SelfSignedCert alongside @RestBootstrap to start the server on HTTPS with auto-configured SSL:

@SelfSignedCert
@RestBootstrap(MyResource.class)
public class SslTest {

    @RestResource
    private Client client;  // auto-configured with client SSL context

    @RestResource
    private URI baseUri;

    @Test
    public void testHttps() {
        assertEquals("https", baseUri.getScheme());
        Response response = client.target(baseUri)
            .path("/hello")
            .request()
            .get();
        assertEquals(200, response.getStatus());
    }
}

When @SelfSignedCert is present alongside @RestBootstrap, the extension automatically:

  • Generates self-signed server and client certificates using the JDK keytool

  • Starts the SeBootstrap instance with HTTPS and the server SSL context

  • Configures injected Client and WebTarget instances with the client SSL context

  • Cleans up all generated keystore files after the test run

  • Tags the test class with ssl-test for JUnit tag filtering

Use the ssl-test tag to include or exclude SSL tests. For example, with Maven Surefire: mvn test -Dgroups=ssl-test (run only SSL tests) or mvn test -DexcludedGroups=ssl-test (skip SSL tests).

4.4.2. Accessing Certificate Artifacts

For advanced scenarios — such as configuring a non-Jakarta REST HTTP client or verifying certificate details — inject the SelfSignedCertificate using @SslCert:

@SelfSignedCert
@RestBootstrap(MyResource.class)
public class SslTest {

    @SslCert
    private SelfSignedCertificate certificate;

    @RestResource
    @RequestPath("hello")
    private URI uri;

    @Test
    public void testWithHttpClient() throws Exception {
        HttpClient httpClient = HttpClient.newBuilder()
            .sslContext(certificate.clientSslContext())
            .build();
        HttpRequest request = HttpRequest.newBuilder()
            .uri(uri)
            .GET()
            .build();
        HttpResponse<String> response = httpClient.send(request,
            HttpResponse.BodyHandlers.ofString());
        assertEquals(200, response.statusCode());
    }

    @Test
    public void testWithKeyStoreApi() {
        try (Client client = ClientBuilder.newBuilder()
                .keyStore(certificate.clientKeyStore(),
                    SelfSignedCertificate.KEYSTORE_PASSWORD)
                .trustStore(certificate.clientTrustStore())
                .build()) {
            // Use client...
        }
    }
}

The SelfSignedCertificate interface provides:

Method

Description

clientSslContext() / serverSslContext()

Pre-configured SSLContext instances

clientKeyStore() / serverKeyStore()

KeyStore objects containing the private key and certificate

clientTrustStore() / serverTrustStore()

KeyStore objects containing the trusted peer’s certificate

clientKeyStorePath() / serverKeyStorePath()

Path to the keystore files on disk

clientTrustStorePath() / serverTrustStorePath()

Path to the truststore files on disk

keyStorePassword()

The password used for all generated stores

keyStoreType()

The keystore type (e.g., PKCS12)

4.4.3. Standalone Certificate Usage

@SelfSignedCert can be used independently of @RestBootstrap when only certificate artifacts are needed:

@SelfSignedCert
public class StandaloneCertTest {

    @SslCert
    private SelfSignedCertificate certificate;

    @Test
    public void testCertificate() {
        assertNotNull(certificate.serverSslContext());
        assertNotNull(certificate.clientSslContext());
        assertTrue(Files.exists(certificate.serverKeyStorePath()));
    }
}

4.4.4. SSL Client Authentication

The sslClientAuthentication attribute on @RestBootstrap controls whether the server requires client certificates during the TLS handshake:

@SelfSignedCert
@RestBootstrap(value = MyResource.class,
    sslClientAuthentication = SSLClientAuthentication.MANDATORY)
public class MutualTlsTest {

    @RestResource
    private Client client;  // configured with full client cert

    @RestResource
    private URI baseUri;

    @Test
    public void clientWithCertSucceeds() {
        // Injected client has client SSL context — succeeds
        Response response = client.target(baseUri)
            .path("/hello")
            .request()
            .get();
        assertEquals(200, response.getStatus());
    }
}

Available modes:

  • SSLClientAuthentication.OPTIONAL (default) — Server requests a client certificate but does not require one. This is the recommended default since the injected client is always configured with the client SSL context.

  • SSLClientAuthentication.MANDATORY — Server requires a valid client certificate. Connections without one are rejected.

  • SSLClientAuthentication.NONE — Server does not request a client certificate.

The sslClientAuthentication attribute is only used by the default ConfigurationProvider. Custom providers specified via configFactory are responsible for their own SSL client authentication configuration.

4.4.5. Custom SSL Configuration

For full control over SSL configuration, use @SslCert field injection in custom providers:

Custom Server Configuration:

public class CustomSslConfigProvider implements ConfigurationProvider {
    @SslCert
    private SelfSignedCertificate certificate;

    @Override
    public SeBootstrap.Configuration getConfiguration(ExtensionContext context) {
        return SeBootstrap.Configuration.builder()
                .protocol("HTTPS")
                .sslContext(certificate.serverSslContext())
                .port(9443)
                .build();
    }
}

@SelfSignedCert
@RestBootstrap(value = MyResource.class,
    configFactory = CustomSslConfigProvider.class)
public class CustomSslTest {
    // Server runs on HTTPS port 9443 with custom configuration
}

Custom Client Configuration:

public class CustomSslClientProvider implements RestClientBuilderProvider {
    @SslCert
    private SelfSignedCertificate certificate;

    @Override
    public ClientBuilder getClientBuilder() {
        return ClientBuilder.newBuilder()
                .sslContext(certificate.clientSslContext())
                .connectTimeout(5, TimeUnit.SECONDS);
    }
}

@SelfSignedCert
@RestBootstrap(MyResource.class)
public class CustomSslClientTest {

    @RestResource
    @RestClientConfig(CustomSslClientProvider.class)
    private Client sslClient;  // custom SSL + timeout config
}

4.5. Nested Test Classes

Nested test classes can inherit or override the parent’s @RestBootstrap:

@RestBootstrap(MyApp.class)
public class OuterTest {

    @RestResource
    private static Client outerClient;

    @Test
    public void testOuter() {
        // Uses MyApp on default port
    }

    @Nested
    class InheritedTests {
        // Inherits parent's @RestBootstrap - shares same instance

        @Test
        public void testInherited(@RestResource Client client) {
            // Same client as outerClient
        }
    }

    @Nested
    @RestBootstrap(value = MyApp.class, configFactory = SecondInstanceConfigurationProvider.class)
    class IsolatedTests {
        // Gets its own SeBootstrap instance with different config

        @Test
        public void testIsolated(@RestResource Client client) {
            // Different client than outerClient
        }
    }
}

5. Extension Points

5.1. Custom Resource Producers

Implement RestResourceProducer to inject custom types:

public class DataSourceProducer implements RestResourceProducer {

    @Override
    public boolean canInject(ExtensionContext context, Class<?> clazz,
                             Annotation... qualifiers) {
        return DataSource.class.isAssignableFrom(clazz);
    }

    @Override
    public Object produce(ExtensionContext context, Class<?> clazz,
                          Annotation... qualifiers) {
        // Create and return your DataSource
        return createDataSource();
    }
}

Registration:

Create META-INF/services/dev.resteasy.junit.extension.api.RestResourceProducer:

com.example.DataSourceProducer

Usage:

@RestBootstrap(MyApp.class)
public class DatabaseTest {

    @RestResource
    private DataSource dataSource;  // Injected by custom producer

    @Test
    public void testDatabase() {
        // Use dataSource...
    }
}

5.2. Resource Scoping

Custom RestResourceProducer implementations can control the lifecycle scope of injected resources by overriding the scope() method. This determines when AutoCloseable resources are automatically closed.

Available Scopes:

  • Scope.DEFAULT (default) - Uses the natural scope of the injection point:

    • Fields (static and instance): Cleaned up when the test class completes

    • Method parameters: Cleaned up when the test method completes

  • Scope.CLASS - Always use class scope:

    • All injections (fields and parameters): Cleaned up when the test class completes

  • Scope.NEW - Create a fresh instance for every injection:

    • No caching - produce() is called for every field and parameter injection

    • Each injection point gets its own isolated instance

When to use each scope:

Use Scope.CLASS for:

  • Expensive-to-create resources (REST clients, database connection pools)

  • Thread-safe, immutable, or stateless resources

  • Resources designed for reuse across multiple test methods

Use Scope.DEFAULT for:

  • Resources that need isolation between test methods (temporary files, test-specific data)

  • Resources that accumulate state or side effects

  • Lightweight resources where recreation cost is negligible

Use Scope.NEW for:

  • Mutable resources that must be isolated per injection (WebTarget, builders)

  • Resources that maintain internal state and cannot be safely shared

  • Resources where each usage context requires a fresh instance

Example: Temporary File Producer with Method-Scoped Cleanup

public class TempFileProducer implements RestResourceProducer {

    @Override
    public boolean canInject(ExtensionContext context, Class<?> clazz,
                             Annotation... qualifiers) {
        return TempFile.class.isAssignableFrom(clazz);
    }

    @Override
    public Object produce(ExtensionContext context, Class<?> clazz,
                          Annotation... qualifiers) {
        return new TempFile(); // AutoCloseable - deletes file on close
    }

    @Override
    public Scope scope() {
        return Scope.DEFAULT; // Method parameters auto-cleanup per method
    }
}

@RestBootstrap(MyApp.class)
public class FileProcessingTest {

    @Test
    public void testMethod1(@RestResource TempFile tempFile) {
        // Fresh temp file created for this method
        tempFile.write("test data");
        // Automatically deleted after this method completes
    }

    @Test
    public void testMethod2(@RestResource TempFile tempFile) {
        // Different temp file from testMethod1
        // Also deleted after this method completes
    }
}

Built-in Producer Scopes:

Built-in producers use the following scopes:

  • Client, URI, Configuration - Use Scope.CLASS (shared for performance, thread-safe)

  • WebTarget - Uses Scope.NEW (mutable, requires isolation per injection)

  • UriBuilder - Always creates fresh instances (mutable)

5.3. Global Configuration Providers

Register a global ConfigurationProvider or RestClientBuilderProvider via ServiceLoader to apply configuration to all tests without specifying it on each annotation.

Create META-INF/services/dev.resteasy.junit.extension.api.ConfigurationProvider:

com.example.GlobalPortProvider

Now all tests without an explicit configFactory will use GlobalPortProvider.

Configuration Precedence:

When determining which configuration to use, the extension follows this precedence (highest to lowest):

  1. Explicit configFactory on @RestBootstrap annotation - full control, ignores all other configuration

  2. Global ConfigurationProvider registered via ServiceLoader

  3. Configuration parameters (dev.resteasy.junit.extension.* properties)

  4. SeBootstrap defaults (random port, localhost, http, root path /)

5.4. Choosing Between value() and application()

Here’s a comparison to help you decide which approach to use:

Use value() when:

// Simple test with just resource classes
@RestBootstrap({UserResource.class, OrderResource.class})
public class SimpleApiTest {
    @RestResource
    private WebTarget target;

    @Test
    public void testGetUser() {
        Response response = target.path("/users/123").request().get();
        assertEquals(200, response.getStatus());
    }
}

Use application() when you need more control:

@ApplicationPath("/api/v1")
public class MyApplication extends Application {
    @Override
    public Set<Class<?>> getClasses() {
        return Set.of(UserResource.class, OrderResource.class, CustomExceptionMapper.class);
    }

    @Override
    public Map<String, Object> getProperties() {
        // Configure application properties
        return Map.of("my.custom.property", "value");
    }
}

@RestBootstrap(application = MyApplication.class)
public class AdvancedApiTest {
    @RestResource
    @RequestPath("/api/app/")
    private WebTarget target;

    @Test
    public void testGetUser() {
        Response response = target.path("/users/123").request().get();
        assertEquals(200, response.getStatus());
    }
}

6. Examples

Complete working examples can be found in the test suite. The test suite demonstrates:

  • ClientInstanceTest - Client injection and instance scoping across all injection points

  • SeBootstrapTest - Basic SeBootstrap usage with various injection types

  • WebTargetTest - WebTarget with @RequestPath in different formats

  • UriInjectionTest - URI injection with and without @RequestPath

  • UriBuilderTest - UriBuilder injection and mutability

  • SharedConfigInstanceTest - Configuration injection and nested test behavior

  • CustomRestResourceProducerTest - Custom RestResourceProducer implementation

  • ResourceScopeTest - Resource lifecycle scoping (CLASS vs DEFAULT)

  • MethodScopedResourceTest - Practical example of method-scoped temporary files

  • SslBootstrapTest - @SelfSignedCert with @RestBootstrap for HTTPS testing

  • SelfSignedCertStandaloneTest - Standalone certificate usage without @RestBootstrap

  • SslClientAuthenticationTest - SSLClientAuthentication modes (OPTIONAL, MANDATORY, NONE)

  • SslCustomConfigProviderTest - Custom ConfigurationProvider with @SslCert injection

  • SslCustomClientProviderTest - Custom RestClientBuilderProvider with @SslCert injection

7. Configuration Reference

7.1. Configuration Parameters

The extension supports the following JUnit configuration parameters. These can be set via system properties (-D flags) or in junit-platform.properties:

Parameter

Description

Default

dev.resteasy.junit.extension.protocol

Protocol (http/https)

http

dev.resteasy.junit.extension.host

Server host

localhost

dev.resteasy.junit.extension.port

Server port (integer)

Random available port

dev.resteasy.junit.extension.root-path

Application root path

/

Usage examples:

# Command line
mvn test -Ddev.resteasy.junit.extension.port=8085

# CI environment avoiding port conflicts
mvn verify -Ddev.resteasy.junit.extension.port=9999 -Ddev.resteasy.junit.extension.host=0.0.0.0
# src/test/resources/junit-platform.properties
dev.resteasy.junit.extension.port=8080
dev.resteasy.junit.extension.root-path=/api/v1

Note: Configuration parameters are ignored when an explicit configFactory is specified on @RestBootstrap.

7.2. SeBootstrap Configuration API

For programmatic configuration, implement ConfigurationProvider. All SeBootstrap.Configuration builder options are available.

Refer to the ConfigurationProvider JavaDoc for implementation details.

8. Frequently Asked Questions

Q: Can I use this with any Jakarta REST implementation?

A: Yes! The extension uses standard Jakarta REST 3.1+ SeBootstrap. Any compliant implementation (RESTEasy, Jersey, etc.) that supports SeBootstrap will work.

Q: How do I test CDI-enabled applications?

A: Use a Jakarta REST implementation that supports CDI, like resteasy-undertow-cdi. The extension uses @RestResource instead of @jakarta.inject.Inject to avoid conflicts with CDI in your application code.

Q: Can I run tests in parallel?

A: Yes, but each test class gets its own SeBootstrap instance. Parallel execution of methods within a single class is not recommended since they share the same server instance.

Q: How do I enable debug logging?

A: Set the dev.resteasy.junit.extension logger to DEBUG level in your logging framework configuration.

Q: Why am I getting "No SeBootstrap instance available"?

A: Ensure your test class is annotated with @RestBootstrap and that you have a Jakarta REST implementation with SeBootstrap support on your test classpath.

Q: Can nested test classes have different configurations?

A: Yes! Add @RestBootstrap with a custom configFactory to the nested class. However, note that some Jakarta REST implementations (like RESTEasy with CDI) may not support running multiple SeBootstrap instances simultaneously due to singleton container limitations.

Q: How do I test with HTTPS/SSL?

A: Add @SelfSignedCert alongside @RestBootstrap on your test class. The extension generates self-signed certificates and configures HTTPS automatically. See SSL/TLS Testing for details and examples.

Q: Can I use my own certificates instead of auto-generated ones?

A: Yes. Implement a custom ConfigurationProvider (for the server) and/or RestClientBuilderProvider (for the client) with your own SSL configuration. You do not need @SelfSignedCert when providing your own certificates.

Q: Are self-signed certificates shared across test classes?

A: Yes. Certificates are generated once per JVM and stored in the root extension context. All test classes annotated with @SelfSignedCert share the same certificate artifacts. This avoids the overhead of running keytool for every test class.

Q: When are injected resources cleaned up?

A: It depends on the producer’s scope:

  • Built-in resources (Client, URI, Configuration): Use CLASS scope - cleaned up when the test class completes, regardless of injection point.

  • Built-in WebTarget: Uses NEW scope - each injection creates a fresh instance. If AutoCloseable, cleaned up based on the injection point (field vs parameter).

  • Custom producers with DEFAULT scope: Fields are cleaned up when the test class completes. Method parameters are cleaned up when the test method completes.

  • Custom producers with CLASS scope: Always cleaned up when the test class completes.

  • Custom producers with NEW scope: Each injection is independent - never cached or reused.

Override scope() in your custom RestResourceProducer to control this behavior.

10. License

This project is licensed under the Apache License v2.0. See the LICENSE file for details.