Internal testing utilities for OpenAI integration with Quarkus LangChain4j
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.
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>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;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");
}
}This testing library is built on WireMock and provides three main components:
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.
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;
}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
);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
);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);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).
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).
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);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;The package includes WireMock stub mappings in /openai/mappings/completions.json that are automatically loaded:
Endpoint: POST /v1/chat/completions
Request Validation: Validates the presence and structure of:
messages array with role and content fieldsmax_tokens, temperature, top_p, n, stream, frequency_penalty, presence_penalty, logit_bias, userResponse: 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"
}]
}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"
}]
}@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);
}@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");
}@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");
});
}@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");
});
}@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();
}@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();
}
}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 classWireMock:
org.wiremock:wiremock-standalone - HTTP mocking framework for intercepting OpenAI API callsRuntime Dependencies (transitive):
org.junit.jupiter:junit-jupiter-api)org.assertj:assertj-core)com.fasterxml.jackson.core:jackson-databind)io.smallrye.config:smallrye-config)<maven.deploy.skip>true</maven.deploy.skip>, intended solely for use within the Quarkus LangChain4j project's test suitesclasspath:/openai automatically by WiremockConfigBuilderCustomizerChatCompletionTransformer uses system properties for configuration, allowing test-specific response content without modifying stub mappingsrestoreOriginalChatCompletionMessageContent()) is automatic via JUnit 5 @AfterEach lifecycle