A scriptable web server for testing HTTP clients with support for HTTP/1.1, HTTP/2, WebSockets, and SSL/TLS
—
Advanced MockWebServer capabilities including custom dispatchers for dynamic response logic, HTTP/2 server push promises, duplex streaming, SSL/TLS configuration, protocol negotiation, and WebSocket upgrades.
Create dynamic response logic by extending the Dispatcher class or using QueueDispatcher with advanced configurations.
/**
* Base class for handling mock server requests with custom logic
*/
abstract class Dispatcher {
/**
* Process incoming request and return appropriate response
* @param request RecordedRequest containing request details
* @returns MockResponse to send back to client
*/
abstract fun dispatch(request: RecordedRequest): MockResponse
/**
* Get preview of next response without consuming it
* @returns MockResponse that would be returned by next dispatch call
*/
open fun peek(): MockResponse
/**
* Release any resources held by the dispatcher
*/
open fun shutdown()
}
/**
* Default dispatcher that serves responses from a FIFO queue
*/
class QueueDispatcher : Dispatcher() {
/**
* Add response to end of queue
* @param response MockResponse to enqueue
*/
open fun enqueueResponse(response: MockResponse)
/**
* Enable/disable fail-fast mode for empty queue
* @param failFast If true, return error response when queue is empty
*/
open fun setFailFast(failFast: Boolean)
/**
* Set specific response for fail-fast mode
* @param failFastResponse Response to return when queue is empty (null for default 404)
*/
open fun setFailFast(failFastResponse: MockResponse?)
}Usage Examples:
// Custom dispatcher for RESTful API simulation
class ApiDispatcher : Dispatcher() {
private val users = mutableMapOf(
1 to """{"id": 1, "name": "Alice"}""",
2 to """{"id": 2, "name": "Bob"}"""
)
override fun dispatch(request: RecordedRequest): MockResponse {
val path = request.path ?: return MockResponse().setResponseCode(400)
return when {
path.matches(Regex("/api/users/\\d+")) && request.method == "GET" -> {
val userId = path.substringAfterLast("/").toIntOrNull()
val user = users[userId]
if (user != null) {
MockResponse()
.setResponseCode(200)
.addHeader("Content-Type", "application/json")
.setBody(user)
} else {
MockResponse().setResponseCode(404)
}
}
path == "/api/users" && request.method == "GET" -> {
val usersList = users.values.joinToString(",", "[", "]")
MockResponse()
.setResponseCode(200)
.addHeader("Content-Type", "application/json")
.setBody(usersList)
}
path == "/api/users" && request.method == "POST" -> {
val body = request.body.readUtf8()
// Simulate user creation
MockResponse()
.setResponseCode(201)
.addHeader("Content-Type", "application/json")
.addHeader("Location", "/api/users/3")
.setBody("""{"id": 3, "name": "New User"}""")
}
else -> MockResponse().setResponseCode(404)
}
}
}
// Apply custom dispatcher
val server = MockWebServer()
server.dispatcher = ApiDispatcher()
server.start()QueueDispatcher Advanced Configuration:
val server = MockWebServer()
val queueDispatcher = QueueDispatcher()
// Configure fail-fast behavior
queueDispatcher.setFailFast(true) // Return 404 when queue is empty
// Or provide custom fail-fast response
queueDispatcher.setFailFast(
MockResponse()
.setResponseCode(503)
.setBody("Service temporarily unavailable")
)
server.dispatcher = queueDispatcher
server.start()Configure HTTP/2 server push promises to simulate modern web server behavior.
/**
* Represents an HTTP/2 server push promise
*/
class PushPromise(
/**
* HTTP method for the pushed request
*/
val method: String,
/**
* Path for the pushed request
*/
val path: String,
/**
* Headers for the pushed request
*/
val headers: Headers,
/**
* Response to be sent for the pushed request
*/
val response: MockResponse
)
/**
* Add HTTP/2 server push promise to response
* @param promise PushPromise to add
* @returns This MockResponse for method chaining
*/
fun withPush(promise: PushPromise): MockResponse
/**
* List of server push promises (read-only)
*/
val pushPromises: List<PushPromise>Usage Examples:
val server = MockWebServer()
// Main page response with pushed resources
val cssContent = "body { font-family: Arial; }"
val jsContent = "console.log('App loaded');"
val mainPageResponse = MockResponse()
.setResponseCode(200)
.addHeader("Content-Type", "text/html")
.setBody("""
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/style.css">
<script src="/app.js"></script>
</head>
<body><h1>Hello World</h1></body>
</html>
""".trimIndent())
.withPush(
PushPromise(
method = "GET",
path = "/style.css",
headers = headingsOf("Content-Type", "text/css"),
response = MockResponse()
.setResponseCode(200)
.addHeader("Content-Type", "text/css")
.setBody(cssContent)
)
)
.withPush(
PushPromise(
method = "GET",
path = "/app.js",
headers = headersOf("Content-Type", "application/javascript"),
response = MockResponse()
.setResponseCode(200)
.addHeader("Content-Type", "application/javascript")
.setBody(jsContent)
)
)
server.enqueue(mainPageResponse)
server.start()Advanced HTTP/2 bidirectional streaming capabilities for testing duplex communication.
/**
* Interface for duplex HTTP/2 response bodies (bidirectional streaming)
*/
interface DuplexResponseBody {
/**
* Handle incoming duplex request with HTTP/2 stream
* @param request RecordedRequest with request details
* @param http2Stream HTTP/2 stream for bidirectional communication
*/
fun onRequest(request: RecordedRequest, http2Stream: Http2Stream)
}
/**
* Scriptable duplex response for HTTP/2 bidirectional streaming
*/
class MockDuplexResponseBody : DuplexResponseBody {
/**
* Expect specific request content
* @param expected Expected request content string
* @returns This MockDuplexResponseBody for method chaining
*/
fun receiveRequest(expected: String): MockDuplexResponseBody
/**
* Expect request stream to be exhausted
* @returns This MockDuplexResponseBody for method chaining
*/
fun exhaustRequest(): MockDuplexResponseBody
/**
* Cancel stream with HTTP/2 error code
* @param errorCode HTTP/2 error code for cancellation
* @returns This MockDuplexResponseBody for method chaining
*/
fun cancelStream(errorCode: ErrorCode): MockDuplexResponseBody
/**
* Expect IOException when reading request
* @returns This MockDuplexResponseBody for method chaining
*/
fun requestIOException(): MockDuplexResponseBody
/**
* Send response data to client
* @param s Response data string
* @param responseSent Optional CountDownLatch for synchronization
* @returns This MockDuplexResponseBody for method chaining
*/
fun sendResponse(s: String, responseSent: CountDownLatch = CountDownLatch(0)): MockDuplexResponseBody
/**
* Close response stream
* @returns This MockDuplexResponseBody for method chaining
*/
fun exhaustResponse(): MockDuplexResponseBody
/**
* Add delay in conversation
* @param duration Delay duration
* @param unit Time unit for duration
* @returns This MockDuplexResponseBody for method chaining
*/
fun sleep(duration: Long, unit: TimeUnit): MockDuplexResponseBody
/**
* Wait for entire duplex conversation to complete successfully
*/
fun awaitSuccess()
}
/**
* Whether response is duplex (HTTP/2 bidirectional, read-only)
*/
val isDuplex: BooleanUsage Examples:
val server = MockWebServer()
// Create duplex response body for chat-like interaction
val duplexBody = MockDuplexResponseBody()
.receiveRequest("HELLO")
.sendResponse("WELCOME")
.receiveRequest("GET_DATA")
.sendResponse("DATA: [1,2,3]")
.receiveRequest("BYE")
.sendResponse("GOODBYE")
.exhaustRequest()
.exhaustResponse()
val duplexResponse = MockResponse()
.setResponseCode(200)
.withDuplexBody(duplexBody)
server.enqueue(duplexResponse)
server.start()
// Client would establish HTTP/2 connection and stream data...
// After client interaction completes:
duplexBody.awaitSuccess() // Verify conversation completed as scriptedConfigure HTTPS with SSL certificates and client authentication options.
/**
* Enable HTTPS with SSL socket factory
* @param sslSocketFactory SSL socket factory for creating SSL connections
* @param tunnelProxy Whether to tunnel through proxy
*/
fun useHttps(sslSocketFactory: SSLSocketFactory, tunnelProxy: Boolean)
/**
* Disable SSL client authentication (default)
*/
fun noClientAuth()
/**
* Request (but don't require) SSL client authentication
*/
fun requestClientAuth()
/**
* Require SSL client authentication
*/
fun requireClientAuth()Usage Examples:
val server = MockWebServer()
// Create SSL context with self-signed certificate
val sslContext = SSLContext.getInstance("TLS")
val keyManager = createSelfSignedKeyManager() // Your implementation
val trustManager = createTrustAllManager() // Your implementation
sslContext.init(arrayOf(keyManager), arrayOf(trustManager), SecureRandom())
// Enable HTTPS
server.useHttps(sslContext.socketFactory, false)
server.requestClientAuth() // Optional client certificates
server.enqueue(MockResponse().setBody("Secure response"))
server.start()
// Now server accepts HTTPS connections
val httpsUrl = server.url("/secure").newBuilder().scheme("https").build()Configure HTTP protocol version support and ALPN negotiation.
/**
* Whether ALPN protocol negotiation is enabled (default: true)
*/
var protocolNegotiationEnabled: Boolean
/**
* Supported protocols for ALPN (default: HTTP/2, HTTP/1.1)
*/
var protocols: List<Protocol>Usage Examples:
val server = MockWebServer()
// Configure protocol support
server.protocolNegotiationEnabled = true
server.protocols = listOf(Protocol.HTTP_2, Protocol.HTTP_1_1)
// Or disable HTTP/2, use only HTTP/1.1
server.protocols = listOf(Protocol.HTTP_1_1)
server.start()Enable WebSocket protocol upgrades for testing WebSocket clients.
/**
* Enable WebSocket upgrade with listener
* @param listener WebSocketListener for handling WebSocket events
* @returns This MockResponse for method chaining
*/
fun withWebSocketUpgrade(listener: WebSocketListener): MockResponseUsage Examples:
val server = MockWebServer()
val webSocketListener = object : WebSocketListener() {
override fun onOpen(webSocket: WebSocket, response: Response) {
webSocket.send("Welcome to WebSocket")
}
override fun onMessage(webSocket: WebSocket, text: String) {
when (text) {
"ping" -> webSocket.send("pong")
"close" -> webSocket.close(1000, "Normal closure")
else -> webSocket.send("Echo: $text")
}
}
}
val webSocketResponse = MockResponse()
.withWebSocketUpgrade(webSocketListener)
server.enqueue(webSocketResponse)
server.start()
// Client connects with WebSocket upgrade request
val request = Request.Builder()
.url(server.url("/websocket"))
.addHeader("Upgrade", "websocket")
.addHeader("Connection", "Upgrade")
.addHeader("Sec-WebSocket-Key", "generated-key")
.addHeader("Sec-WebSocket-Version", "13")
.build()Configure HTTP/2 settings frames and 1xx informational responses.
/**
* Set HTTP/2 settings frame
* @param settings HTTP/2 settings to apply
* @returns This MockResponse for method chaining
*/
fun withSettings(settings: Settings): MockResponse
/**
* Add 1xx informational response before main response
* @param informationalResponse MockResponse with 1xx status code
* @returns This MockResponse for method chaining
*/
fun addInformationalResponse(informationalResponse: MockResponse): MockResponseUsage Examples:
// HTTP/2 settings configuration
val settingsFrame = Settings().apply {
set(Settings.MAX_CONCURRENT_STREAMS, 100)
set(Settings.INITIAL_WINDOW_SIZE, 65535)
}
val http2Response = MockResponse()
.setResponseCode(200)
.withSettings(settingsFrame)
.setBody("HTTP/2 response with custom settings")
// 1xx informational responses
val mainResponse = MockResponse()
.addInformationalResponse(
MockResponse().setResponseCode(100) // 100 Continue
)
.addInformationalResponse(
MockResponse().setResponseCode(102) // 102 Processing
)
.setResponseCode(200)
.setBody("Final response after informational responses")
server.enqueue(mainResponse)MockDuplexResponseBody enables scriptable bidirectional HTTP/2 streaming for testing duplex communication patterns.
/**
* A scriptable request/response conversation for HTTP/2 duplex streaming.
* Create conversation scripts by calling methods in the sequence they should execute.
*/
class MockDuplexResponseBody : DuplexResponseBody {
/**
* Expect to receive specific request data
* @param expected Expected request content as string
* @returns This MockDuplexResponseBody for method chaining
*/
fun receiveRequest(expected: String): MockDuplexResponseBody
/**
* Expect the request body to be exhausted (no more data)
* @returns This MockDuplexResponseBody for method chaining
*/
fun exhaustRequest(): MockDuplexResponseBody
/**
* Cancel the HTTP/2 stream with specified error code
* @param errorCode HTTP/2 error code for stream cancellation
* @returns This MockDuplexResponseBody for method chaining
*/
fun cancelStream(errorCode: ErrorCode): MockDuplexResponseBody
/**
* Expect an IOException when reading request data
* @returns This MockDuplexResponseBody for method chaining
*/
fun requestIOException(): MockDuplexResponseBody
/**
* Send response data to client
* @param s Response content as string
* @param responseSent Optional latch signaled when response is sent
* @returns This MockDuplexResponseBody for method chaining
*/
fun sendResponse(s: String, responseSent: CountDownLatch = CountDownLatch(0)): MockDuplexResponseBody
/**
* Close the response stream (no more data will be sent)
* @returns This MockDuplexResponseBody for method chaining
*/
fun exhaustResponse(): MockDuplexResponseBody
/**
* Sleep for specified duration during conversation
* @param duration Duration to sleep
* @param unit Time unit for duration
* @returns This MockDuplexResponseBody for method chaining
*/
fun sleep(duration: Long, unit: TimeUnit): MockDuplexResponseBody
/**
* Wait for duplex conversation to complete successfully
* Blocks until all scripted actions are executed
*/
fun awaitSuccess()
}Usage Examples:
import okhttp3.internal.http2.ErrorCode
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
// Create duplex conversation script
val duplexBody = MockDuplexResponseBody()
.receiveRequest("Initial request data")
.sendResponse("Server acknowledges")
.receiveRequest("Follow-up data")
.sendResponse("Processing complete")
.exhaustRequest()
.exhaustResponse()
// Create duplex response
val duplexResponse = MockResponse()
.setResponseCode(200)
.addHeader("Content-Type", "application/octet-stream")
.setDuplexResponseBody(duplexBody)
server.enqueue(duplexResponse)
// Client performs duplex communication...
// Wait for conversation to complete
duplexBody.awaitSuccess()
// Stream cancellation example
val cancellingDuplexBody = MockDuplexResponseBody()
.receiveRequest("Start streaming")
.sendResponse("Streaming started")
.cancelStream(ErrorCode.CANCEL)
// Error handling example
val errorDuplexBody = MockDuplexResponseBody()
.receiveRequest("Valid data")
.sendResponse("Data received")
.requestIOException() // Expect IOException on next read
.exhaustResponse()Install with Tessl CLI
npx tessl i tessl/maven-com-squareup-okhttp3--mockwebserver