如何对调用同服务网格内部非暴露API的Scala函数做单元测试?
如何Mock同一服务网格内未暴露的API来测试Scala函数
当然可以通过Mock实现这类测试,下面是两种最可行的解决方案:
方案一:依赖注入+Mock框架(推荐)
原代码的问题在于直接硬编码URL并创建HttpURLConnection,导致无法直接替换依赖。解决思路是把HTTP请求逻辑抽离成可注入的依赖,测试时用Mock框架替换真实实现。
步骤1:抽离HTTP客户端接口
先定义一个抽象的HTTP客户端 trait,封装请求逻辑:
trait HttpClient { def sendRequest(url: String, requestPayload: String): HttpURLConnection }
步骤2:重构原函数依赖注入
修改internalApiCall,让它依赖这个trait,而不是直接创建连接:
// 生产环境实现真实的HttpClient class RealHttpClient extends HttpClient { override def sendRequest(url: String, requestPayload: String): HttpURLConnection = { val conn = new URL(url).openConnection().asInstanceOf[HttpURLConnection] // 补充设置请求方法、写入payload等逻辑 conn } } // 原函数改为默认依赖注入,不影响生产调用 def internalApiCall(payload: String, client: HttpClient = new RealHttpClient()): Unit = { val request = buildRequestForApi(payload) val response = client.sendRequest("http://service.namespace.svc.cluster.local:8080/path", request) // 响应码断言逻辑 assert(response.getResponseCode == 200) }
步骤3:编写Mock测试
用ScalaMock或Mockito创建HttpClient的Mock实例,预设返回值并验证调用:
import org.scalamock.scalatest.MockFactory import org.scalatest.funsuite.AnyFunSuite class InternalApiTest extends AnyFunSuite with MockFactory { test("internalApiCall should pass when API returns 200") { // 创建Mock的HttpClient和HttpURLConnection val mockClient = mock[HttpClient] val mockConn = mock[HttpURLConnection] // 预设Mock连接返回200响应码 (mockConn.getResponseCode _).returns(200) // 预设sendRequest返回Mock连接 (mockClient.sendRequest(_: String, _: String)).returns(mockConn) // 执行测试 internalApiCall("test-payload", mockClient) // 验证HttpClient的sendRequest是否被正确调用 (mockClient.sendRequest _).verify( "http://service.namespace.svc.cluster.local:8080/path", buildRequestForApi("test-payload") ) } test("internalApiCall should fail when API returns 500") { val mockClient = mock[HttpClient] val mockConn = mock[HttpURLConnection] (mockConn.getResponseCode _).returns(500) (mockClient.sendRequest(_: String, _: String)).returns(mockConn) // 断言抛出预期的断言错误 assertThrows[AssertionError] { internalApiCall("test-payload", mockClient) } } }
方案二:嵌入式Mock HTTP服务器(最小代码改动)
如果不想大规模重构原代码,可以用嵌入式HTTP服务器(如WireMock、OkHttp MockWebServer)在测试时启动一个本地Mock服务,替换原函数的目标URL。
步骤1:引入WireMock依赖
在sbt构建文件中添加测试依赖:
libraryDependencies += "com.github.tomakehurst" % "wiremock-jre8" % "2.35.0" % Test
步骤2:编写测试用例
启动本地Mock服务,配置预期的响应,并替换原函数的URL:
import com.github.tomakehurst.wiremock.WireMockServer import com.github.tomakehurst.wiremock.client.WireMock import org.scalatest.BeforeAndAfterAll import org.scalatest.funsuite.AnyFunSuite class InternalApiTest extends AnyFunSuite with BeforeAndAfterAll { private val mockServer = new WireMockServer(8081) // 本地测试端口 override def beforeAll(): Unit = { mockServer.start() WireMock.configureFor("localhost", 8081) // 配置Mock服务接收POST请求并返回200 WireMock.stubFor(WireMock.post(WireMock.urlEqualTo("/path")) .willReturn(WireMock.aResponse().withStatus(200))) } override def afterAll(): Unit = { mockServer.stop() } test("internalApiCall handles successful response from mock API") { // 临时替换原函数中的硬编码URL(建议把URL改成可配置项,测试时注入本地地址) // 示例:假设原URL从配置读取,这里临时修改配置 // Config.apiUrl = "http://localhost:8081/path" // 执行测试 internalApiCall("test-payload") // 验证Mock服务是否收到了预期的请求 WireMock.verify(WireMock.postRequestedFor(WireMock.urlEqualTo("/path")) .withRequestBody(WireMock.equalTo(buildRequestForApi("test-payload")))) } }
总结
- 依赖注入+Mock框架的方式更灵活,适合长期维护的代码,能完全隔离外部依赖;
- 嵌入式Mock服务器适合快速测试,无需大规模重构,但需要启动本地服务,测试速度略慢。
内容的提问来源于stack exchange,提问作者Abhishek
相关产品推荐
相关产品推荐

