如何在 Vert.x 中对控制器级别进行单元测试

How to unit test on controller level in Vert.x

用户控制器:

class UserController(private val graphRepository: GraphRepository) : Controller {

    override fun installRoutes(router: Router) {
        router.install {
            post("/api/v1/user").handler(this@UserController::addUser)
        }
    }
}

正在测试路由并调用路由处理程序"addUser":

@Test
    fun newUserAdded() {
        Mockito.`when`(mockRoutingContext.queryParam("id")).thenReturn(listOf("1"))
        Mockito.`when`(mockGraphRepository.getUser("1")).thenReturn(Promise.ofSuccess(null))
        Mockito.`when`(mockGraphRepository.enrollUser(any())).thenReturn(Promise.ofSuccess(Unit))
        Mockito.`when`(mockRoutingContext.response()).thenReturn(mockHttpServerResponse)
        Mockito.doNothing().`when`(mockHttpServerResponse).end()

        UserController(mockGraphRepository).addUser(mockRoutingContext)

        Mockito.verify(mockRoutingContext, Mockito.times(1)).response()
        Mockito.verify(mockHttpServerResponse).end()
    } 

主要问题是如何在不在 "UserController" 上显式调用 "addUser" 的情况下测试控制器路由,因为我想将控制器功能设为私有。

出于各种原因,通常不鼓励对您不拥有的类型进行模拟行为,例如(但不限于):

  • 如果模拟依赖项的实际实现发生变化,模拟的行为将不会自动显示任何前瞻性更改。
  • 测试引入的模拟越多,测试承载的认知负荷就越大,并且一些测试需要大量模拟才能工作。

最适合我的方法是将这些更多地视为集成测试,同时避免模拟。

为了实现这一点,我得到了一个摘要 VertxPlatform class,我对其进行了扩展,其中包含对我在各种测试中经常引用的资源的引用:

  • Vertx 实例本身
  • 一个Router
  • 一个EventBus
  • 一个HttpServer
  • 一个WebClient

每次调用每个测试都会重新初始化这些资源,并且 Router 随后与 HttpServer.

相关联

一个典型的测试最终看起来像这样:

class MyHandlerIT : VertxPlatform() {
  private lateinit var myHandler: MyHandler  // <-- the component under test

  @Before override fun setUp(context: TestContext) {
    super.setUp(context)  // <-- reinitializes all the underlying Vert.x components

    myHandler = MyHandler()

    router.post("/my/handler/path")
        .handler(myHandler.validationHandler())
        .handler(myHandler.requestHandler(vertx))
        .failureHandler(myHandler.failureHandler())
  }

  @After override fun tearDown(context: TestContext) {
    super.tearDown(context)
  }

  @Test fun status_400_on_some_condition(context: TestContext) {
    val async = context.async()

    testRequest(POST, path = "/my/handler/path", params = null, body = null, headers = null)
        .subscribeBy(
            onSuccess = { response ->
              context.assertEquals(BAD_REQUEST.code(), response.statusCode())

              async.complete()
            },
            onError = { error ->
              context.fail(error)
            }
        )
  }
}

在每个单独的测试中,您可能会有更多针对特定案例的设置。例如,如果 MyHandler 通过 EventBus 从您的 GraphRepository 获得结果,您可以在该测试范围内设置一个伪造的 Consumer服务器返回您试图模拟的值。

希望这会有所帮助,或者至少能激发一些想法!