웹소켓을 모킹없이 수신메시지를 검사하는 방법을 먼저알아보고 웹소켓 핸들러에 연결된 액터모델만 테스트하기 위해 웹소켓만 모킹을 한후 액터모델 수신메시지 검사를 하는방법을 알아보겠습니다. |
이전장 : 분산처리 Reactive 웹소켓 by 액터모델
class SocketActorHandlerTest {
companion object {
private lateinit var client: OkHttpClient
private lateinit var request: Request
private val receivedMessages = mutableListOf<String>()
@BeforeAll
@JvmStatic
fun setup() {
client = OkHttpClient()
request = Request.Builder().url("ws://localhost:8080/ws-actor").build()
}
}
fun assertContainsText(text: String) {
val objectMapper = jacksonObjectMapper()
val messages = receivedMessages.map { objectMapper.readValue<Map<String, Any>>(it)["message"] as String }
if (messages.any { it.contains(text) }) {
return
}
throw AssertionError("The list does not contain the text '$text'")
} |
@Test
fun testWebSocketConnection() {
val latch = CountDownLatch(1) // 1초동안 수신대기목적
val listener = object : WebSocketListener() {
override fun onOpen(webSocket: WebSocket, response: Response) {
webSocket.send("hello")
}
override fun onMessage(webSocket: WebSocket, text: String) {
println("Received message: $text")
receivedMessages.add(text)
latch.countDown()
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
t.printStackTrace()
latch.countDown()
}
}
client.newWebSocket(request, listener)
latch.await(10, TimeUnit.SECONDS)
assertContainsText("You are connected")
} |
pub/sub을 포함 다양한 시나리오에서 메시지를 검사할수 있으며 Queue를 사용해 하나씩 꺼내어 검사하면
순차검증 모드로 개선할수 있습니다.
ActorTestKit을 이용하는 경우 관찰자를 통해 유사한 방식으로 수신메시지를 꺼내 코어로직 블락킹없는 순차검증이 가능합니다.
class SessionManagerActorTest {
companion object {
private val testKit = ActorTestKit.create()
@BeforeAll
@JvmStatic
fun setup() {
// Setup code if needed
}
@AfterAll
@JvmStatic
fun tearDown() {
testKit.shutdownTestKit()
}
} |
@Test
fun testAddSession() {
val sessionManagerActor: ActorRef<UserSessionCommand> = testKit.spawn(SessionManagerActor.create(), "session-manager-actor")
val probe: TestProbe<UserSessionCommandResponse> = testKit.createTestProbe()
val session = Mockito.mock(WebSocketSession::class.java)
Mockito.`when`(session.id).thenReturn("session1")
val message = Mockito.mock(WebSocketMessage::class.java)
Mockito.`when`(session.textMessage(Mockito.anyString())).thenReturn(message)
Mockito.`when`(session.send(Mockito.any())).thenReturn(Mono.empty())
sessionManagerActor.tell(UserSessionCommand.AddSession(session, probe.ref))
probe.expectMessage(Information("Session added session1"))
} |
이벤트 드리븐 방식을 이용할때, 메시지큐 작동 코드를 블락하지 않고 수신메시지를 검사할수 있는 기능은 중요할수 있습니다.
각자의 고유 메일박스(메시지큐)를 가진 액터모델을 유닛테스트하는, 여기서 소개된 ActorTestKit 컨셉도 함께 살펴보겠습니다.
일반적인 동기 테스트에서는 Thread.sleep() 같은 비효율적인 방법을 사용해야 하지만, ActorTestKit은 내부적으로 TestProbe와 ask-pattern을 활용하여 비동기 메시지 기반 시스템을 효과적으로 테스트할 수 있습니다.
TestProbe를 활용하면 특정 액터의 반응을 검증할 수 있음.awaitAssert 같은 기능을 사용해 폴링(polling) 없이 테스트 결과를 안전하게 검증할 수 있음.ActorTestKit은 테스트마다 새로운 Actor 시스템을 생성하여 실행되므로, 공유 상태(shared state)로 인해 발생할 수 있는 문제를 방지할 수 있습니다.
ActorSystem이 제공되므로 테스트 간 간섭 방지.TestKit.shutdownTestKit()을 통해 테스트가 끝난 후 자원을 자동 정리 가능.class SampleActorTest : WordSpec({
val testKit = ActorTestKit.create()
val probe = testKit.createTestProbe<String>()
"SampleActor" should {
"reply with expected message" {
val sampleActor = testKit.spawn(SampleActor())
sampleActor.tell("ping", probe.ref)
probe.expectMessage("pong")
}
}
afterTest {
testKit.shutdownTestKit()
}
})
|
ActorTestKit은 Future, ask-pattern, TestProbe 등의 기능을 활용하여 블로킹 없이도 메시지의 처리 결과를 검증할 수 있습니다.
ask-pattern을 사용하면 CompletableFuture 또는 Deferred를 반환받아 비동기적으로 결과를 확인할 수 있음.TestProbe.expectMessage()를 사용해 특정 메시지가 도착할 때까지 기다릴 수 있음.val responseFuture = testKit.spawn(SampleActor()).ask { replyTo ->
SampleActor.Message("ping", replyTo)
}
val response = responseFuture.await()
assertEquals("pong", response)
|
ActorTestKit은 기본적으로 타임아웃을 설정할 수 있어, 특정 시간이 지나도 메시지가 도착하지 않으면 테스트를 실패로 처리할 수 있습니다.
expectMessage(Duration.ofSeconds(2)) 같은 기능을 사용해 일정 시간 내 응답이 없으면 실패 처리.테스트 환경에서 시간 제어가 필요할 때 TestKit은 **가짜 시간(Fake Time)**을 활용할 수 있습니다.
TestKit.scheduler().advance()를 사용해 특정 시간 후의 동작을 테스트 가능.Scheduler.scheduleOnce()와 함께 미래의 이벤트를 미리 트리거할 수 있음.val probe = testKit.createTestProbe<String>()
val scheduler = testKit.scheduler()
scheduler.scheduleOnce(100.milliseconds, probe.ref) { "delayed-message" }
probe.expectMessage("delayed-message") // 100ms 후 메시지가 도착하는지 검증
|
| 장점 | 설명 |
|---|---|
| 비동기 메시지 기반 테스트 | TestProbe와 ask-pattern을 활용해 자연스럽게 비동기 시스템을 검증 가능 |
| 독립적인 Actor 시스템 제공 | 테스트 간 공유 상태 문제 없이 독립적 실행 |
| 블로킹 없이 테스트 가능 | ask와 expectMessage를 활용해 Future 기반으로 동작 |
| 타임아웃 기반 신뢰성 | 메시지 수신 여부를 일정 시간 내 검증 가능 |
| 가짜 시간(Fake Time) 활용 가능 | TestScheduler로 지연 메시지 및 타이머 이벤트 테스트 가능 |
즉, ActorTestKit은 비동기 Actor 시스템을 효율적이고 안정적으로 검증할 수 있는 강력한 테스트 도구입니다. 🚀