OpenAI

Maven Central

AI-Mocks OpenAI is a specialized mock server implementation for mocking the OpenAI API, built using Mokksy.

MockOpenai is tested against official openai-java SDK and popular JVM AI frameworks: LangChain4j and Spring AI.

Currently, it supports:

Quick Start

Include the library in your test dependencies (Maven or Gradle).

build.gradle.kts
1testImplementation("dev.mokksy.aimocks:ai-mocks-openai-jvm:$latestVersion")
pom.xml
1<dependency>
2  <groupId>dev.mokksy.aimocks</groupId>
3  <artifactId>ai-mocks-openai-jvm</artifactId>
4  <version>[LATEST_VERSION]</version>
5  <scope>test</scope>
6</dependency>

Chat Completions API

Set up a mock server and define mock responses:

1val openai = MockOpenai(verbose = true)

Let's simulate OpenAI Chat Completions API:

 1// Define mock response
 2openai.completion {
 3  temperature = 0.7
 4  seed = 42
 5  model = "gpt-4o-mini"
 6  maxTokens = 100
 7  topP = 0.95
 8  systemMessageContains("helpful assistant")
 9  userMessageContains("say 'Hello!'")
10} responds {
11  assistantContent = "Hello"
12  finishReason = "stop"
13  delay = 200.milliseconds // delay before answer
14}
15
16// OpenAI client setup
17val client: OpenAIClient =
18  OpenAIOkHttpClient
19    .builder()
20    .apiKey("dummy-api-key")
21    .baseUrl(openai.baseUrl()) // connect to mock OpenAI
22    .responseValidation(true)
23    .build()
24
25// Use the mock endpoint
26val params =
27  ChatCompletionCreateParams
28    .builder()
29    .temperature(0.7)
30    .maxCompletionTokens(100)
31    .topP(0.95)
32    .messages(
33      listOf(
34        ChatCompletionMessageParam.ofSystem(
35          ChatCompletionSystemMessageParam
36            .builder()
37            .content(
38              "You are a helpful assistant.",
39            ).build(),
40        ),
41        ChatCompletionMessageParam.ofUser(
42          ChatCompletionUserMessageParam
43            .builder()
44            .content("Just say 'Hello!' and nothing else")
45            .build(),
46        ),
47      ),
48    ).model("gpt-4o-mini")
49    .build()
50
51val result: ChatCompletion =
52  client
53    .chat()
54    .completions()
55    .create(params)
56
57println(result)

Mocking Negative Scenarios

With AI-Mocks it is possible to test negative scenarios, such as erroneous responses and delays.

Custom Error Response

 1openai.completion {
 2  temperature = 0.7
 3  seed = 42
 4  model = "gpt-4o-mini"
 5  maxTokens = 100
 6  systemMessageContains("helpful assistant")
 7  userMessageContains("say 'Hello!'")
 8}.respondsError(String::class) {
 9  body =
10    // language=json
11    """
12    {
13      "type": "error",
14      "code": "ERR_SOMETHING",
15      "message": "Arrr, blast me barnacles! This be not what ye expect! 🏴‍☠️",
16      "param": null
17    }
18    """.trimIndent()
19  contentType = ContentType.Text.Plain
20  delay = 100.milliseconds
21  httpStatus = HttpStatusCode.PreconditionFailed
22}

OpenAI-Compatible Error Response

 1openai.completion {
 2  temperature = 0.7
 3  seed = 42
 4  model = "gpt-4o-mini"
 5  maxTokens = 100
 6  systemMessageContains("helpful assistant")
 7  userMessageContains("say 'Hello!'")
 8}.respondsError(String::class) {
 9  body =
10    // language=json
11    """
12    {
13        "error": {
14           "type": "server_error",
15          "code": "ERR_SOMETHING",
16          "message": "Arrr, blast me barnacles! This be not what ye expect! 🏴‍☠️",
17          "param": "foo"
18        }
19    }
20    """.trimIndent()
21  delay = 150.milliseconds
22  contentType = ContentType.Application.Json
23  httpStatus = HttpStatusCode.InternalServerError
24}

Integration with LangChain4j

You may use also LangChain4J Kotlin Extensions:

 1val model: OpenAiChatModel =
 2  OpenAiChatModel
 3    .builder()
 4    .apiKey("dummy-api-key")
 5    .baseUrl(openai.baseUrl())
 6    .build()
 7
 8val result =
 9  model.chat {
10    parameters =
11      OpenAiChatRequestParameters
12        .builder()
13        .temperature(0.7)
14        .modelName("gpt-4o-mini")
15        .maxCompletionTokens(100)
16        .topP(0.95)
17        .seed(42)
18        .build()
19    messages += userMessage("Say Hello")
20  }
21
22println(result)

Stream Responses

Mock streaming responses easily with flow support or a list of chunks.

Streaming with List of Chunks

 1openai.completion {
 2  temperature = 0.7
 3  model = "gpt-4o-mini"
 4  topP = 0.95
 5} respondsStream {
 6  responseChunks = listOf("All", " we", " need", " is", " Love")
 7  delay = 50.milliseconds
 8  delayBetweenChunks = 10.milliseconds
 9  finishReason = "stop"
10}
11
12// Create OpenAI client
13val client: OpenAIClient =
14  OpenAIOkHttpClient
15    .builder()
16    .apiKey("dummy-key")
17    .baseUrl(openai.baseUrl())
18    .build()
19
20// Make streaming request
21val params =
22  ChatCompletionCreateParams
23    .builder()
24    .temperature(0.7)
25    .topP(0.95)
26    .messages(
27      listOf(
28        ChatCompletionMessageParam.ofUser(
29          ChatCompletionUserMessageParam
30            .builder()
31            .content("What do we need?")
32            .build(),
33        ),
34      ),
35    ).model("gpt-4o-mini")
36    .build()
37
38val result = StringBuilder()
39client
40  .chat()
41  .completions()
42  .createStreaming(params)
43  .use { response ->
44    response
45      .stream()
46      .flatMap { it.choices().stream() }
47      .flatMap { it.delta().content().stream() }
48      .forEach { result.append(it) }
49  }
50
51// Result: "All we need is Love"

Streaming with Kotlin Flow

 1openai.completion {
 2  temperature = 0.7
 3  model = "gpt-4o-mini"
 4} respondsStream {
 5  responseFlow =
 6    flow {
 7      emit("All")
 8      emit(" we")
 9      emit(" need")
10      emit(" is")
11      emit(" Love")
12    }
13  delay = 60.milliseconds
14  delayBetweenChunks = 15.milliseconds
15  finishReason = "stop"
16}

Integration with Spring-AI

To test Spring-AI integration:

 1// create mock server
 2val openai = MockOpenai(verbose = true)
 3
 4// create Spring-AI client
 5val chatClient =
 6  ChatClient
 7    .builder(
 8      org.springframework.ai.openai.OpenAiChatModel
 9        .builder()
10        .openAiApi(
11          OpenAiApi
12            .builder()
13            .apiKey("demo-key")
14            .baseUrl(openai.baseUrl())
15            .build(),
16        ).build(),
17    ).build()
18
19// Set up a mock for the LLM call
20openai.completion {
21  temperature = 0.7
22  seed = 42
23  model = "gpt-4o-mini"
24  maxTokens = 100
25  topP = 0.95
26  topK = 40
27  systemMessageContains("helpful pirate")
28  userMessageContains("say 'Hello!'")
29} responds {
30  assistantContent = "Ahoy there, matey! Hello!"
31  finishReason = "stop"
32  delay = 200.milliseconds
33}
34
35// Configure Spring-AI client call
36val response =
37  chatClient
38    .prompt()
39    .system("You are a helpful pirate")
40    .user("Just say 'Hello!'")
41    .options<OpenAiChatOptions>(
42      OpenAiChatOptions
43        .builder()
44        .maxCompletionTokens(100)
45        .temperature(0.7)
46        .topP(0.95)
47        .model("gpt-4o-mini")
48        .seed(42)
49        .build(),
50    )
51    // Make a call
52    .call()
53    .chatResponse()
54
55// Verify the response
56response?.result shouldNotBe null
57response?.result?.apply {
58metadata.finishReason shouldBe "STOP"
59output.text shouldBe "Ahoy there, matey! Hello!"
60}

Check for examples in the integration tests.

Embeddings API

Mock the OpenAI Embeddings API to test your embeddings generation:

Basic Embedding Response

 1// Set up mock server
 2val openai = MockOpenai(verbose = true)
 3
 4// Define mock response for embedding request
 5openai.embeddings {
 6    model = "text-embedding-3-small"
 7    inputContains("Hello")
 8    stringInput("Hello world")
 9} responds {
10    delay = 200.milliseconds
11    embeddings(
12        listOf(0.1f, 0.2f, 0.3f)
13    )
14}
15
16// Create OpenAI client
17val client: OpenAIClient =
18    OpenAIOkHttpClient
19        .builder()
20        .apiKey("dummy-key")
21        .baseUrl(openai.baseUrl())
22        .responseValidation(true)
23        .build()
24
25// Make embedding request
26val params = EmbeddingCreateParams
27    .builder()
28    .model("text-embedding-3-small")
29    .input(EmbeddingCreateParams.Input.ofString("Hello world"))
30    .build()
31
32val result = client
33    .embeddings()
34    .create(params)
35
36// Verify results
37result.model() // "text-embedding-3-small"
38result.data()[0].embedding() // [0.1, 0.2, 0.3]
39result.data()[0].index() // 0

Multiple Embeddings

You can mock multiple embeddings for batch input:

 1openai.embeddings {
 2    model = "text-embedding-3-small"
 3    stringListInput(listOf("Hello", "world"))
 4} responds {
 5    delay = 100.milliseconds
 6    embeddings(
 7        listOf(0.1f, 0.2f, 0.3f),
 8        listOf(0.4f, 0.5f, 0.6f)
 9    )
10}
11
12val params = EmbeddingCreateParams
13    .builder()
14    .model("text-embedding-3-small")
15    .input(EmbeddingCreateParams.Input.ofArrayOfStrings(listOf("Hello", "world")))
16    .build()
17
18val result = client
19    .embeddings()
20    .create(params)
21
22// Returns 2 embeddings
23result.data().size // 2
24result.data()[0].embedding() // [0.1, 0.2, 0.3]
25result.data()[1].embedding() // [0.4, 0.5, 0.6]

Advanced Input Matching

You can use inputContains() to match requests where the input contains specific substrings:

1openai.embeddings {
2    model = "text-embedding-3-small"
3    inputContains("Hello")
4    inputContains("world")
5    stringInput("Hello world")
6} responds {
7    embeddings(listOf(0.1f, 0.2f, 0.3f))
8}

Error Scenarios

Test error handling for embeddings:

 1openai.embeddings {
 2    model = "text-embedding-3-small"
 3    stringInput("boom")
 4}.respondsError(String::class) {
 5    body = "Kaboom!"
 6    contentType = ContentType.Text.Plain
 7    httpStatus = HttpStatusCode.BadRequest
 8    delay = 200.milliseconds
 9}
10
11// This will throw BadRequestException
12val params = EmbeddingCreateParams
13    .builder()
14    .model("text-embedding-3-small")
15    .input(EmbeddingCreateParams.Input.ofString("invalid input"))
16    .build()
17
18try {
19    client.embeddings().create(params)
20} catch (e: BadRequestException) {
21    // Handle error
22}

Moderations API

Mock the OpenAI Moderations API to test content moderation:

Basic Moderation Response

 1// Set up mock server
 2val openai = MockOpenai(verbose = true)
 3
 4// Define mock response for moderation request
 5openai.moderation {
 6    model = "omni-moderation-latest"
 7    inputContains("Hello world")
 8} responds {
 9    flagged = true
10    delay = 200.milliseconds
11    category(name = "harassment", score = 0.1, inputTypes = listOf(TEXT))
12    category(
13        name = ModerationCategory.SEXUAL,
14        score = 0.2,
15        inputTypes = listOf(TEXT, InputType.IMAGE)
16    )
17}
18
19// Create OpenAI client
20val client: OpenAIClient =
21    OpenAIOkHttpClient
22        .builder()
23        .apiKey("dummy-key")
24        .baseUrl(openai.baseUrl())
25        .responseValidation(true)
26        .build()
27
28// Make moderation request
29val params =
30    ModerationCreateParams
31        .builder()
32        .model("omni-moderation-latest")
33        .input("Hello world")
34        .build()
35
36val result = client
37    .moderations()
38    .create(params)
39
40// Verify results
41result.model() // "omni-moderation-latest"
42result.results()[0].flagged() // true
43result.results()[0].categories().harassment() // true
44result.results()[0].categoryScores().harassment() // 0.1
45result.results()[0].categoryAppliedInputTypes().harassment() // [TEXT]

Moderation Error Scenarios

 1openai.moderation {
 2    model = "omni-moderation-latest"
 3    inputContains("boom")
 4}.respondsError(String::class) {
 5    body = "Kaboom!"
 6    contentType = ContentType.Text.Plain
 7    httpStatus = HttpStatusCode.BadRequest
 8    delay = 200.milliseconds
 9}
10
11// This will throw BadRequestException
12val params = ModerationCreateParams
13    .builder()
14    .model("omni-moderation-latest")
15    .input("boom")
16    .build()
17
18try {
19    client.moderations().create(params)
20} catch (e: BadRequestException) {
21    // Handle error
22}