如何在 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
服务器返回您试图模拟的值。
希望这会有所帮助,或者至少能激发一些想法!
用户控制器:
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
服务器返回您试图模拟的值。
希望这会有所帮助,或者至少能激发一些想法!