Testing HTTP with StubHttpServer
yaes-http-test-scalatest provides StubHttpServerSpec, a ScalaTest mixin trait that spins up a lightweight in-process HTTP stub server backed by the JDK’s built-in com.sun.net.httpserver.HttpServer. It captures incoming requests and returns configurable responses — no external process or dependency required.
Installation
Section titled “Installation”Add the dependency to your build.sbt:
libraryDependencies += "in.rcard.yaes" %% "yaes-http-test-scalatest" % "0.21.0" % TestThis module depends only on ScalaTest — no λÆS runtime dependency is pulled in transitively.
Check Maven Central for the latest version.
Quick Start
Section titled “Quick Start”Mix StubHttpServerSpec into your spec class to get a running stub server wired into the ScalaTest lifecycle:
import in.rcard.yaes.test.http.scalatest.{StubHttpServerSpec, StubResponse}import org.scalatest.flatspec.AnyFlatSpecimport org.scalatest.matchers.should.Matchersimport java.net.URIimport java.net.http.{HttpClient, HttpRequest}import java.net.http.HttpResponse.BodyHandlers
class MyHttpClientSpec extends AnyFlatSpec with Matchers with StubHttpServerSpec {
private val http = HttpClient.newHttpClient()
"MyHttpClient" should "call the correct endpoint" in { stubServer.setHandler(_ => StubResponse(200, """{"status":"ok"}"""))
val request = HttpRequest .newBuilder(URI.create(s"$stubBaseUrl/api/resource")) .GET() .build() http.send(request, BodyHandlers.ofString())
stubServer.capturedRequests.head.path shouldBe "/api/resource" }}Lifecycle
Section titled “Lifecycle”The mixin manages the server automatically:
| Event | What happens |
|---|---|
| Suite start | Server binds to an ephemeral port and starts |
| Before each test | Captured requests cleared, handler reset to default (500) |
| After all tests | Server stopped, port released |
No manual setup or teardown is needed.
stubServer and stubBaseUrl
Section titled “stubServer and stubBaseUrl”StubHttpServerSpec exposes two members:
val stubServer: StubHttpServer // the server instancedef stubBaseUrl: String // e.g. "http://localhost:54321"Use stubBaseUrl when constructing request URIs so tests automatically use the ephemeral port.
Configuring Responses
Section titled “Configuring Responses”Call setHandler with a function from CapturedRequest to StubResponse:
stubServer.setHandler { req => if req.path == "/api/users" then StubResponse(200, """[{"id":1}]""", Map("Content-Type" -> "application/json")) else StubResponse(404, "not found")}StubResponse has three fields:
case class StubResponse( statusCode: Int, body: String, headers: Map[String, String] = Map.empty)If no handler is configured (or after a reset), the server returns 500 with body "no handler configured".
Inspecting Captured Requests
Section titled “Inspecting Captured Requests”After making HTTP calls, read capturedRequests to assert on what was received:
val captured = stubServer.capturedRequestscaptured should have size 1captured.head.method shouldBe "GET"captured.head.path shouldBe "/api/users"captured.head.rawQuery shouldBe Some("page=1")CapturedRequest contains:
case class CapturedRequest( method: String, path: String, rawQuery: Option[String], headers: Map[String, List[String]], // header names are lower-cased body: String)Requirements
Section titled “Requirements”- Java 25+: Required by λÆS for virtual threads and structured concurrency
- Scala 3.8.1+: Uses Scala 3 syntax
- ScalaTest 3.x: Included transitively