CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-quarkiverse-langchain4j--quarkus-langchain4j-openai-testing-internal

Internal testing utilities for OpenAI integration with Quarkus LangChain4j

Overview
Eval results
Files

Quarkus LangChain4j OpenAI Testing Internal

Internal testing utilities for the Quarkus LangChain4j OpenAI integration. This package provides WireMock-based test infrastructure that enables developers to write integration tests for OpenAI API interactions without making actual API calls. It includes a base test class with assertion helpers, a response transformer for dynamic test configuration, and pre-configured WireMock mappings for common OpenAI endpoints.

Package Information

  • Package Name: quarkus-langchain4j-openai-testing-internal
  • Group ID: io.quarkiverse.langchain4j
  • Package Type: maven
  • Language: Java
  • Installation: Add to pom.xml in test scope:
<dependency>
    <groupId>io.quarkiverse.langchain4j</groupId>
    <artifactId>quarkus-langchain4j-openai-testing-internal</artifactId>
    <version>1.7.4</version>
    <scope>test</scope>
</dependency>

Core Imports

import io.quarkiverse.langchain4j.openai.testing.internal.OpenAiBaseTest;
import io.quarkiverse.langchain4j.openai.testing.internal.ChatCompletionTransformer;
import io.quarkiverse.langchain4j.openai.testing.internal.OpenAiBaseTest.MessageContent;

Basic Usage

import io.quarkiverse.langchain4j.openai.testing.internal.OpenAiBaseTest;
import org.junit.jupiter.api.Test;
import java.util.Map;

public class MyOpenAiTest extends OpenAiBaseTest {

    @Test
    public void testChatCompletion() throws Exception {
        // Set expected chat completion response
        setChatCompletionMessageContent("Hello from OpenAI!");

        // Execute your test code that calls OpenAI API
        // The WireMock server will intercept requests

        // Validate the request that was sent
        Map<String, Object> requestAsMap = getRequestAsMap();
        assertSingleRequestMessage(requestAsMap, "Test prompt");
    }
}

Architecture

This testing library is built on WireMock and provides three main components:

  • OpenAiBaseTest: Base test class that extends WiremockAware, providing assertion utilities and request inspection methods for validating OpenAI API interactions
  • ChatCompletionTransformer: WireMock response transformer that dynamically injects chat completion content via system properties, enabling test-specific response configuration
  • WiremockConfigBuilderCustomizer: Automatic configuration of WireMock DevServices with response templating, extension scanning, and pre-configured OpenAI endpoint mappings

Tests extend OpenAiBaseTest to inherit WireMock infrastructure and assertion helpers. The ChatCompletionTransformer automatically loads as a WireMock extension and transforms responses based on test configuration. Pre-configured stub mappings for /v1/chat/completions and /v1/images/generations endpoints are automatically loaded from the classpath.

Capabilities

Base Test Class

OpenAiBaseTest provides the foundation for OpenAI integration tests, extending WiremockAware with OpenAI-specific assertion utilities.

public abstract class OpenAiBaseTest extends WiremockAware {

    /**
     * Jackson ObjectMapper instance for JSON processing.
     * Initialized by beforeAll() lifecycle method.
     * Can be used by test subclasses for custom JSON operations.
     */
    static ObjectMapper mapper;

    /**
     * JUnit 5 lifecycle method that initializes Jackson ObjectMapper for JSON processing.
     * Annotated with @BeforeAll and called automatically before all tests in the class.
     */
    @BeforeAll
    static void beforeAll();

    /**
     * Sets the expected chat completion message content for the current test.
     * This content will be injected into WireMock responses via ChatCompletionTransformer.
     *
     * @param content The message content to return in chat completion responses
     */
    protected void setChatCompletionMessageContent(String content);

    /**
     * JUnit 5 lifecycle method that automatically clears chat completion message content
     * after each test. Annotated with @AfterEach and called automatically.
     */
    @AfterEach
    public void restoreOriginalChatCompletionMessageContent();

    /**
     * Converts the single logged request body to a Map for assertion purposes.
     * Uses Jackson ObjectMapper for JSON deserialization.
     *
     * @return The request body as a Map<String, Object>
     * @throws IOException If JSON parsing fails
     */
    protected Map<String, Object> getRequestAsMap() throws IOException;

    /**
     * Converts raw request body bytes to a Map for assertion purposes.
     * Uses Jackson ObjectMapper for JSON deserialization.
     *
     * @param body The raw request body bytes
     * @return The request body as a Map<String, Object>
     * @throws IOException If JSON parsing fails
     */
    protected Map<String, Object> getRequestAsMap(byte[] body) throws IOException;
}

Message Assertion Helpers

Validate chat completion request message structures.

/**
 * Asserts that the request contains a single message with role "user"
 * and the specified content value.
 *
 * @param requestAsMap The parsed request body as a Map
 * @param value The expected message content
 */
protected static void assertSingleRequestMessage(
    Map<String, Object> requestAsMap,
    String value
);

/**
 * Asserts that the request contains multiple messages matching the provided list.
 * Each MessageContent record specifies the expected role and content for a message.
 *
 * @param requestAsMap The parsed request body as a Map
 * @param messageContents List of expected messages with their roles and content
 */
protected static void assertMultipleRequestMessage(
    Map<String, Object> requestAsMap,
    List<MessageContent> messageContents
);

/**
 * Generic assertion helper for custom message validations.
 * Provides access to the messages array for custom assertion logic.
 *
 * @param requestAsMap The parsed request body as a Map
 * @param messagesAssertions Consumer function to perform custom assertions on messages
 */
protected static void assertMessages(
    Map<String, Object> requestAsMap,
    Consumer<List<? extends Map>> messagesAssertions
);

Function Call Assertion Helpers

Validate function/tool call structures in chat completion requests.

/**
 * Asserts that the request contains a single function/tool with the specified name.
 *
 * @param requestAsMap The parsed request body as a Map
 * @param functionName The expected function/tool name
 */
protected static void assertSingleFunction(
    Map<String, Object> requestAsMap,
    String functionName
);

/**
 * Generic assertion helper for custom function/tool validations.
 * Provides access to the tools/functions array for custom assertion logic.
 *
 * @param requestAsMap The parsed request body as a Map
 * @param functionAssertions Consumer function to perform custom assertions on functions
 */
protected static void assertFunctions(
    Map<String, Object> requestAsMap,
    Consumer<List<? extends Map>> functionAssertions
);

WireMock Infrastructure (Inherited)

OpenAiBaseTest inherits WireMock testing infrastructure from WiremockAware.

/**
 * Returns WireMock URL template for configuration properties.
 * Template: "http://localhost:${quarkus.wiremock.devservices.port}"
 *
 * @return URL template string for use in configuration
 */
public static String wiremockUrlForConfig();

/**
 * Returns WireMock URL with path template for configuration properties.
 *
 * @param path The path to append to the URL template
 * @return URL template string with path for use in configuration
 */
public static String wiremockUrlForConfig(String path);

/**
 * Returns the resolved WireMock URL at runtime.
 *
 * @return The actual WireMock URL with resolved port
 */
public String resolvedWiremockUrl();

/**
 * Returns the resolved WireMock URL with path at runtime.
 *
 * @param path The path to append to the URL
 * @return The actual WireMock URL with path
 */
public String resolvedWiremockUrl(String path);

/**
 * Gets or creates a WireMock client instance.
 *
 * @return WireMock client for interacting with the mock server
 */
protected WireMock wiremock();

/**
 * Gets or creates a WireMock client instance for a specific port.
 *
 * @param port The port number for the WireMock instance
 * @return WireMock client for the specified port
 */
protected WireMock wiremock(Integer port);

/**
 * Gets the resolved WireMock port from configuration.
 *
 * @return The port number WireMock is running on
 */
protected Integer getResolvedWiremockPort();

/**
 * Clears all logged requests in WireMock.
 * Useful for cleaning up between test scenarios.
 */
protected void resetRequests();

/**
 * Clears all stub mappings in WireMock.
 * Useful for removing default stubs and configuring custom behavior.
 */
protected void resetMappings();

/**
 * Gets the single logged request, asserting that exactly one request exists.
 *
 * @return The single logged request
 * @throws AssertionError if the number of requests is not exactly 1
 */
protected LoggedRequest singleLoggedRequest();

/**
 * Gets the single logged request from a specific WireMock instance.
 *
 * @param wireMock The WireMock client instance to query
 * @return The single logged request
 * @throws AssertionError if the number of requests is not exactly 1
 */
protected LoggedRequest singleLoggedRequest(WireMock wireMock);

/**
 * Gets the request body of the single logged request.
 *
 * @return The request body as a byte array
 * @throws AssertionError if the number of requests is not exactly 1
 */
protected byte[] requestBodyOfSingleRequest();

/**
 * Gets request body from a ServeEvent.
 *
 * @param serveEvent The serve event containing request information
 * @return The request body as a byte array
 */
protected byte[] getRequestBody(ServeEvent serveEvent);

Chat Completion Response Transformer

Dynamic response transformation for chat completion tests.

public class ChatCompletionTransformer implements ResponseDefinitionTransformerV2 {

    /**
     * Creates a new ChatCompletionTransformer instance.
     * This constructor is called automatically by WireMock's extension loading mechanism
     * via service loader (META-INF/services).
     */
    public ChatCompletionTransformer();

    /**
     * Transforms response definitions by injecting chat completion message content
     * if the system property is set. This method is called automatically by WireMock
     * for responses configured to use the "completion-transformer".
     *
     * @param serveEvent The serve event containing request and response information
     * @return The transformed response definition
     */
    public ResponseDefinition transform(ServeEvent serveEvent);

    /**
     * Returns the transformer name used to reference this transformer in stub mappings.
     *
     * @return "completion-transformer"
     */
    public String getName();

    /**
     * Sets the chat completion message content via system property.
     * The content will be injected into responses by the transformer.
     * This is typically called from test code or via OpenAiBaseTest.setChatCompletionMessageContent().
     *
     * @param content The message content to inject into responses
     */
    public static void setContent(String content);

    /**
     * Clears the chat completion message content by removing the system property.
     * This is typically called from test cleanup code.
     */
    public static void clearContent();
}

The transformer is automatically registered as a WireMock Extension via service loader mechanism (META-INF/services/com.github.tomakehurst.wiremock.extension.Extension).

WireMock Configuration

Automatic configuration of WireMock DevServices for OpenAI testing.

public class WiremockConfigBuilderCustomizer implements SmallRyeConfigBuilderCustomizer {

    /**
     * Creates a new WiremockConfigBuilderCustomizer instance.
     * This constructor is called automatically during Quarkus configuration phase
     * via service loader (META-INF/services).
     */
    public WiremockConfigBuilderCustomizer();

    /**
     * Configures WireMock DevServices with OpenAI-specific settings.
     * This method is called automatically during Quarkus configuration phase.
     *
     * Sets the following properties:
     * - quarkus.wiremock.devservices.global-response-templating: "true"
     * - quarkus.wiremock.devservices.extension-scanning-enabled: "true"
     * - quarkus.wiremock.devservices.files-mapping: "classpath:/openai"
     *
     * Priority: 500
     *
     * @param builder The SmallRye configuration builder
     */
    public void configBuilder(final SmallRyeConfigBuilder builder);
}

The customizer is automatically registered via service loader mechanism (META-INF/services/io.smallrye.config.SmallRyeConfigBuilderCustomizer).

Types

MessageContent

Record type for specifying expected message content in assertions.

public static record MessageContent(String role, String content) {
    /**
     * Creates a MessageContent record with role and content.
     *
     * @param role The role of the message (e.g., "user", "assistant", "system", "function")
     * @param content The message content (can be null)
     */
}

Usage example:

List<MessageContent> expected = List.of(
    new MessageContent("system", "You are a helpful assistant"),
    new MessageContent("user", "What is the weather?"),
    new MessageContent("assistant", "I'll help you check the weather")
);
assertMultipleRequestMessage(requestAsMap, expected);

External Types

Types from external libraries used in the public API:

// WireMock types
import com.github.tomakehurst.wiremock.extension.ResponseDefinitionTransformerV2;
import com.github.tomakehurst.wiremock.stubbing.ServeEvent;
import com.github.tomakehurst.wiremock.http.ResponseDefinition;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.verification.LoggedRequest;

// SmallRye Config types
import io.smallrye.config.SmallRyeConfigBuilder;
import io.smallrye.config.SmallRyeConfigBuilderCustomizer;

// Jackson types
import wiremock.com.fasterxml.jackson.databind.ObjectMapper;

// Standard Java types
import java.util.Map;
import java.util.List;
import java.util.function.Consumer;
import java.io.IOException;

Pre-configured Mock Endpoints

The package includes WireMock stub mappings in /openai/mappings/completions.json that are automatically loaded:

Chat Completions Endpoint

Endpoint: POST /v1/chat/completions

Request Validation: Validates the presence and structure of:

  • messages array with role and content fields
  • Optional parameters: max_tokens, temperature, top_p, n, stream, frequency_penalty, presence_penalty, logit_bias, user

Response: Returns a chat completion response with the content set via ChatCompletionTransformer. The response uses the template variable {{parameters.ChatCompletionMessageContent}} which is populated from the system property.

Example response structure:

{
  "id": "chatcmpl-123",
  "object": "chat.completion",
  "created": 1677652288,
  "model": "gpt-3.5-turbo",
  "choices": [{
    "index": 0,
    "message": {
      "role": "assistant",
      "content": "{{parameters.ChatCompletionMessageContent}}"
    },
    "finish_reason": "stop"
  }]
}

Image Generation Endpoint

Endpoint: POST /v1/images/generations

Request Validation: Validates the presence and structure of:

  • model (string)
  • prompt (string)
  • n (integer, optional)
  • size (string, optional)
  • quality (string, optional)
  • style (string, optional)
  • response_format (string, optional)

Response: Returns a sample image generation response with a placeholder URL and revised prompt.

Example response structure:

{
  "created": 1677652288,
  "data": [{
    "url": "https://example.com/image.png",
    "revised_prompt": "A revised version of the prompt"
  }]
}

Advanced Usage Examples

Testing Multiple Messages

@Test
public void testMultipleMessages() throws Exception {
    setChatCompletionMessageContent("I can help with that!");

    // Execute test code

    // Validate request had multiple messages
    Map<String, Object> requestAsMap = getRequestAsMap();
    List<MessageContent> expected = List.of(
        new MessageContent("system", "You are a helpful assistant"),
        new MessageContent("user", "Help me with Java")
    );
    assertMultipleRequestMessage(requestAsMap, expected);
}

Testing Function Calls

@Test
public void testFunctionCall() throws Exception {
    setChatCompletionMessageContent("Calling function...");

    // Execute test code that includes function calling

    // Validate request included the expected function
    Map<String, Object> requestAsMap = getRequestAsMap();
    assertSingleFunction(requestAsMap, "get_weather");
}

Custom Message Assertions

@Test
public void testCustomMessageValidation() throws Exception {
    setChatCompletionMessageContent("Response content");

    // Execute test code

    Map<String, Object> requestAsMap = getRequestAsMap();
    assertMessages(requestAsMap, messages -> {
        assertThat(messages).hasSizeGreaterThan(1);
        assertThat(messages.get(0)).containsEntry("role", "system");
    });
}

Custom Function Assertions

@Test
public void testCustomFunctionValidation() throws Exception {
    setChatCompletionMessageContent("Function result");

    // Execute test code

    Map<String, Object> requestAsMap = getRequestAsMap();
    assertFunctions(requestAsMap, functions -> {
        assertThat(functions).hasSize(2);
        assertThat(functions.get(0))
            .containsEntry("type", "function")
            .extracting("function")
            .asInstanceOf(MAP_STRING_OBJECT)
            .containsEntry("name", "get_weather");
    });
}

Using WireMock Infrastructure Directly

@Test
public void testWithDirectWireMockAccess() throws Exception {
    // Reset any existing stubs
    resetMappings();

    // Configure custom stub
    wiremock().register(
        post(urlEqualTo("/v1/chat/completions"))
            .willReturn(aResponse()
                .withStatus(200)
                .withBody("{\"custom\": \"response\"}"))
    );

    // Execute test code

    // Get logged request
    LoggedRequest request = singleLoggedRequest();
    assertThat(request.getUrl()).isEqualTo("/v1/chat/completions");

    // Clean up
    resetRequests();
}

Accessing WireMock URL in Configuration

@TestConfiguration
public class TestConfig {

    @Bean
    public OpenAIClient openAIClient() {
        // Use WireMock URL in configuration
        String baseUrl = OpenAiBaseTest.wiremockUrlForConfig();

        // Configure client with template URL
        // At runtime, this will resolve to actual WireMock port
        return OpenAIClient.builder()
            .baseUrl(baseUrl)
            .build();
    }
}

Dependencies

This package requires the following dependencies:

Core Testing Infrastructure:

  • io.quarkiverse.langchain4j:quarkus-langchain4j-testing-internal (v1.7.4) - Parent testing utilities providing WiremockAware base class

WireMock:

  • org.wiremock:wiremock-standalone - HTTP mocking framework for intercepting OpenAI API calls

Runtime Dependencies (transitive):

  • JUnit 5 - Testing framework (org.junit.jupiter:junit-jupiter-api)
  • AssertJ - Fluent assertions (org.assertj:assertj-core)
  • Jackson - JSON processing (com.fasterxml.jackson.core:jackson-databind)
  • SmallRye Config - Configuration management (io.smallrye.config:smallrye-config)

Notes

  • This is an internal testing dependency marked with <maven.deploy.skip>true</maven.deploy.skip>, intended solely for use within the Quarkus LangChain4j project's test suites
  • The package provides maximum reusability in testing scenarios where developers need to validate OpenAI API integration behavior
  • All WireMock configuration and extension loading happens automatically via service loader mechanisms
  • Pre-configured stub mappings are loaded from classpath:/openai automatically by WiremockConfigBuilderCustomizer
  • The ChatCompletionTransformer uses system properties for configuration, allowing test-specific response content without modifying stub mappings
  • Test cleanup (restoreOriginalChatCompletionMessageContent()) is automatic via JUnit 5 @AfterEach lifecycle

Install with Tessl CLI

npx tessl i tessl/maven-io-quarkiverse-langchain4j--quarkus-langchain4j-openai-testing-internal@1.7.0
Workspace
tessl
Visibility
Public
Created
Last updated