You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为Akka HTTP端点编写集成测试并启动本地应用?

Akka HTTP端点集成测试实现方案

问题背景

在Java中可通过Dropwizard Rule启动本地API服务,直接对本地端点发起请求完成集成测试,同时配置好所需环境变量。现在需要在Akka HTTP中实现类似效果,通过启动本地应用测试/jobs端点,但不清楚如何在测试前启动服务。

控制器代码

@GET
@Path("/jobs")
@Consumes(Array(MediaType.APPLICATION_JSON))
@Produces(Array(MediaType.APPLICATION_JSON))
@Operation(
  summary = "See Status of All Workflow Jobs",
  description = "See Status of All Workflow Jobs.",
  method = "GET",
  security = Array(new SecurityRequirement(name = "bearerAuth")))
def getJobs: Route = jobs

现有测试代码(未启动服务)

"The service" should {
  "return jobs response" in {
    val headers =
      RawHeader("Authorization", "Bearer token")
    val httpRequest =
      HttpRequest(
        HttpMethods.GET,
        "http://localhost:8080/jobs?ids=20388241",
        scala.collection.immutable.Seq(headers))

    def sendRequest(): Future[String] = {
      val responseFuture: Future[HttpResponse] = Http().singleRequest(httpRequest)
      Await.result(responseFuture, 5.seconds)
      val entityFuture: Future[HttpEntity.Strict] =
        responseFuture.flatMap(response => response.entity.toStrict(10.seconds))
      entityFuture.map(entity => entity.data.utf8String)
    }

    val mapper = new ObjectMapper
    mapper.registerModule(DefaultScalaModule)

    sendRequest().map(response =>
      assert(
        mapper.readValue(response, classOf[WorkflowsResponse]).workflows.get(0).workflowId > 0))
  }
}

修改后的完整测试代码

使用ScalaTest的BeforeAndAfterAll特质管理服务生命周期,实现测试前启动、测试后关闭本地服务:

import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.{HttpMethods, HttpRequest, RawHeader}
import akka.http.scaladsl.server.Route
import akka.stream.ActorMaterializer
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import org.scalatest.BeforeAndAfterAll
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import scala.concurrent.duration._
import scala.concurrent.{Await, Future}

class JobServiceIntegrationSpec extends AnyWordSpec with Matchers with BeforeAndAfterAll {
  implicit val system: ActorSystem = ActorSystem("JobServiceTestSystem")
  implicit val materializer: ActorMaterializer = ActorMaterializer()
  import system.dispatcher

  // 替换为你实际的路由实例
  val jobRoutes: Route = YourController.getJobs

  var serverBinding: Http.ServerBinding = _
  val testPort = 8080
  val baseUrl = s"http://localhost:$testPort"

  override def beforeAll(): Unit = {
    // 启动Akka HTTP服务并绑定端口
    val bindingFuture = Http().bindAndHandle(jobRoutes, "localhost", testPort)
    serverBinding = Await.result(bindingFuture, 10.seconds)
  }

  override def afterAll(): Unit = {
    // 关闭服务与ActorSystem,清理资源
    val shutdownFuture = serverBinding.unbind().flatMap(_ => system.terminate())
    Await.result(shutdownFuture, 10.seconds)
  }

  "The jobs service" should {
    "return valid workflow responses" in {
      val headers = RawHeader("Authorization", "Bearer token")
      val httpRequest = HttpRequest(
        method = HttpMethods.GET,
        uri = s"$baseUrl/jobs?ids=20388241",
        headers = Seq(headers)
      )

      def sendRequest(): Future[String] = {
        Http().singleRequest(httpRequest)
          .flatMap(_.entity.toStrict(10.seconds))
          .map(_.data.utf8String)
      }

      val mapper = new ObjectMapper().registerModule(DefaultScalaModule)
      val response = Await.result(sendRequest(), 10.seconds)
      val workflowsResponse = mapper.readValue(response, classOf[WorkflowsResponse])
      
      workflowsResponse.workflows.get(0).workflowId should be > 0
    }
  }
}

关键说明

  • 服务生命周期控制:通过beforeAll启动服务,afterAll关闭服务与ActorSystem,确保测试前后资源完全清理。
  • 路由绑定:将YourController.getJobs替换为真实的业务路由实例,保证测试逻辑与生产环境一致。
  • 异步逻辑优化:移除原测试中冗余的Await.result调用,通过链式调用简化Future处理;测试断言阶段使用Await.result获取同步结果,适配ScalaTest同步测试范式(若需全异步测试,可改用AsyncWordSpec)。
  • 环境变量配置:若需设置测试环境变量,可在beforeAll中通过System.setProperty加载测试配置:
    // 设置环境变量
    System.setProperty("TEST_ENV_VAR", "test-value")
    // 或加载测试专属配置文件
    val config = ConfigFactory.load("test.conf")
    implicit val system: ActorSystem = ActorSystem("JobServiceTestSystem", config)
    

内容的提问来源于stack exchange,提问作者user3897533

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 18:25:01