From 99b1ca51f39c49c18d94686425ba2aa47b76e451 Mon Sep 17 00:00:00 2001 From: KaiRaiChu Date: Thu, 22 May 2025 20:04:53 +0100 Subject: [PATCH 1/3] #156: Move mock messages to use chat repository abstractions --- .../kotlin/chatserver/ChatMessageResult.kt | 16 ++++ .../kotlin/chatserver/ChatRepositories.kt | 4 + src/main/kotlin/chatserver/ChatServer.kt | 3 +- .../chatserver/messages/ChatServerMessages.kt | 8 ++ .../chatserver/messages/LocalChatMessages.kt | 88 +++++++++++++++++++ 5 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/chatserver/ChatMessageResult.kt create mode 100644 src/main/kotlin/chatserver/messages/ChatServerMessages.kt create mode 100644 src/main/kotlin/chatserver/messages/LocalChatMessages.kt diff --git a/src/main/kotlin/chatserver/ChatMessageResult.kt b/src/main/kotlin/chatserver/ChatMessageResult.kt new file mode 100644 index 0000000..d8b1470 --- /dev/null +++ b/src/main/kotlin/chatserver/ChatMessageResult.kt @@ -0,0 +1,16 @@ +package chatserver + +import chatserver.ReadChatRepository.ReadResult + +data class ChatMessageResult( + override val isOk: Boolean, + override val item: String, + override val error: Exception?, +) : + ReadResult { + companion object { + fun ok(item: String): ChatMessageResult = ChatMessageResult(true, item, null) + + fun fail(e: Exception? = null): ChatMessageResult = ChatMessageResult(false, "", e) + } +} diff --git a/src/main/kotlin/chatserver/ChatRepositories.kt b/src/main/kotlin/chatserver/ChatRepositories.kt index 9ae4cf0..be5bea8 100644 --- a/src/main/kotlin/chatserver/ChatRepositories.kt +++ b/src/main/kotlin/chatserver/ChatRepositories.kt @@ -1,13 +1,17 @@ package chatserver +import chatserver.messages.LocalChatMessages import chatserver.profiles.LocalProfilesRepository class ChatRepositories( private val profiles: LocalProfilesRepository, + private val messages: LocalChatMessages, ) { fun writeProfiles(): WriteChatRepository = profiles fun readProfiles(): ReadChatRepository = profiles fun subscribeProfiles(): SubscribeChatRepository = profiles + + fun readMessages(): ReadChatRepository = messages } diff --git a/src/main/kotlin/chatserver/ChatServer.kt b/src/main/kotlin/chatserver/ChatServer.kt index 19e9b15..cbe14a9 100644 --- a/src/main/kotlin/chatserver/ChatServer.kt +++ b/src/main/kotlin/chatserver/ChatServer.kt @@ -2,6 +2,7 @@ package chatserver import chatserver.MessagesRepository.Read import chatserver.MessagesRepository.Write +import chatserver.messages.chatServerMessagesModule import chatserver.profiles.chatServerProfilesModule import org.koin.core.module.dsl.factoryOf import org.koin.dsl.binds @@ -9,7 +10,7 @@ import org.koin.dsl.module val chatServerModule = module { - includes(chatServerProfilesModule) + includes(chatServerProfilesModule, chatServerMessagesModule) factory { MockMessages } binds arrayOf(Read::class, Write::class) factoryOf(::ChatRepositories) } diff --git a/src/main/kotlin/chatserver/messages/ChatServerMessages.kt b/src/main/kotlin/chatserver/messages/ChatServerMessages.kt new file mode 100644 index 0000000..2f1386c --- /dev/null +++ b/src/main/kotlin/chatserver/messages/ChatServerMessages.kt @@ -0,0 +1,8 @@ +package chatserver.messages + +import org.koin.dsl.module + +val chatServerMessagesModule = + module { + single { LocalChatMessages(get()) } + } diff --git a/src/main/kotlin/chatserver/messages/LocalChatMessages.kt b/src/main/kotlin/chatserver/messages/LocalChatMessages.kt new file mode 100644 index 0000000..71382d5 --- /dev/null +++ b/src/main/kotlin/chatserver/messages/LocalChatMessages.kt @@ -0,0 +1,88 @@ +package chatserver.messages + +import arch.RokyDispatchers +import chatserver.ChatMessageResult +import chatserver.ReadChatRepository +import chatserver.SubscribeChatRepository +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch +import kotlin.time.Duration.Companion.seconds + +class LocalChatMessages( + private val dispatchers: RokyDispatchers, + private val scope: CoroutineScope = CoroutineScope(dispatchers.default + Job()), + private val messages: List = sampleMessages, + private val users: List = sampleUsers, +) : ReadChatRepository, SubscribeChatRepository { + private var samples: Job? = null + private val _events = MutableStateFlow("") + private val events = _events.asStateFlow() + + override fun latest(): ChatMessageResult = events.toResult() + + override fun observe(): Flow = events.map { ChatMessageResult.ok(it) } + + override fun subscribe() { + samples = + scope.launch { + flow { + while (true) { + delay(3.seconds) + emit("${users.random()}: ${messages.random()}") + } + }.cancellable().collect { + _events.value = it + } + } + } + + override fun unsubscribe() { + samples?.cancel() + } + + companion object { + private fun StateFlow.toResult(): ChatMessageResult = ChatMessageResult.ok(value) + + private val sampleMessages = + listOf( + "This is a coup!", + "What time's Roky Coding tonight?", + "Look at the calendar...", + "Charizard", + "Remind me to get my washing at 4 PM", + "ASMR....", + "BRAIN...praise me", + "Biggleswade is naff", + "Biggleswade is amazing /s", + "I love Biggleswade!!!", + "AHHHHHHHHHHHHHHHHHH", + "Wordle: 4/6", + "Wordle: 1/6", + "Wordle: 2/6", + "I AM SO HUNGRY RN", + "I am feeling quiet today", + ":thumbs_up:", + "I just have a bit of a cold rn", + "I just think it's something going around", + "Don't just type out what I'm saying Mike", + ":breathing_noises:", + ) + + val sampleUsers = + listOf( + "Martine", + "Ed", + "Kai", + "Terry", + "Robert", + "Tom", + "Brian", + "Dunia", + "Stefano", + "Mike", + ) + } +} From 9b8df2adeea362ade0fd4cc1e9cf664e29a04dda Mon Sep 17 00:00:00 2001 From: KaiRaiChu Date: Wed, 28 May 2025 18:22:21 +0100 Subject: [PATCH 2/3] #156: Replace mock messages in view message usecase --- .../sendmessages/SendMessagePresenter.kt | 5 +- .../chatroom/sendmessages/SendMessages.kt | 1 - .../kotlin/chatroom/users/UsersListUseCase.kt | 4 +- .../chatroom/viewmessages/ViewMessages.kt | 5 +- .../viewmessages/ViewMessagesPresenter.kt | 22 ++- .../viewmessages/ViewMessagesUseCase.kt | 66 --------- .../kotlin/chatserver/ChatRepositories.kt | 2 + src/main/kotlin/chatserver/ChatServer.kt | 3 - .../kotlin/chatserver/MessagesRepository.kt | 13 -- src/main/kotlin/chatserver/MockMessages.kt | 20 --- .../chatserver/messages/LocalChatMessages.kt | 17 ++- .../profiles/LocalProfilesRepository.kt | 4 +- .../sendmessages/SendMessagePresenterTest.kt | 12 +- .../viewmessages/ViewMessagesPresenterTest.kt | 58 ++++++-- .../viewmessages/ViewMessagesUseCaseTest.kt | 137 +++++++++--------- .../messages/LocalChatMessagesTest.kt | 116 +++++++++++++++ 16 files changed, 275 insertions(+), 210 deletions(-) delete mode 100644 src/main/kotlin/chatroom/viewmessages/ViewMessagesUseCase.kt delete mode 100644 src/main/kotlin/chatserver/MessagesRepository.kt delete mode 100644 src/main/kotlin/chatserver/MockMessages.kt create mode 100644 src/test/kotlin/chatserver/messages/LocalChatMessagesTest.kt diff --git a/src/main/kotlin/chatroom/sendmessages/SendMessagePresenter.kt b/src/main/kotlin/chatroom/sendmessages/SendMessagePresenter.kt index a726a93..f826435 100644 --- a/src/main/kotlin/chatroom/sendmessages/SendMessagePresenter.kt +++ b/src/main/kotlin/chatroom/sendmessages/SendMessagePresenter.kt @@ -4,14 +4,13 @@ import arch.Presenter import arch.RokyDispatchers import chatroom.sendmessages.SendMessageEvent.SendMessage import chatroom.sendmessages.SendMessageViewState.Clear -import chatserver.MessagesRepository import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext class SendMessagePresenter( private val windowScope: CoroutineScope, - private val send: MessagesRepository.Write, + private val send:(String)->Unit = ::println, dispatchers: RokyDispatchers, ) : Presenter(dispatchers) { override fun onAttach(view: SendMessagesView) { @@ -25,7 +24,7 @@ class SendMessagePresenter( fun onEvent(event: SendMessageEvent) { if (event is SendMessage) { windowScope.launch(dispatchers.io) { - send.send(event.message) + send(event.message) withContext(dispatchers.main) { withView { it.show(Clear) } } diff --git a/src/main/kotlin/chatroom/sendmessages/SendMessages.kt b/src/main/kotlin/chatroom/sendmessages/SendMessages.kt index 3ecafa7..8a22ad7 100644 --- a/src/main/kotlin/chatroom/sendmessages/SendMessages.kt +++ b/src/main/kotlin/chatroom/sendmessages/SendMessages.kt @@ -9,7 +9,6 @@ val sendMessagesModule = scoped { SendMessagePresenter( dispatchers = get(), - send = get(), windowScope = get().windowScope, ) } diff --git a/src/main/kotlin/chatroom/users/UsersListUseCase.kt b/src/main/kotlin/chatroom/users/UsersListUseCase.kt index 1c5800d..ea9b00e 100644 --- a/src/main/kotlin/chatroom/users/UsersListUseCase.kt +++ b/src/main/kotlin/chatroom/users/UsersListUseCase.kt @@ -1,6 +1,6 @@ package chatroom.users -import chatroom.viewmessages.ViewMessagesUseCase +import chatserver.messages.LocalChatMessages import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow @@ -8,7 +8,7 @@ import kotlin.random.Random import kotlin.time.Duration.Companion.seconds class UsersListUseCase( - private val users: List = ViewMessagesUseCase.sampleUsers, + private val users: List = LocalChatMessages.sampleUsers, private val rndInt: () -> Int = { Random.nextInt(4, users.size - 1) }, private val rndUser: (List) -> String = { it.random() }, ) { diff --git a/src/main/kotlin/chatroom/viewmessages/ViewMessages.kt b/src/main/kotlin/chatroom/viewmessages/ViewMessages.kt index a2f7c5f..e14bb92 100644 --- a/src/main/kotlin/chatroom/viewmessages/ViewMessages.kt +++ b/src/main/kotlin/chatroom/viewmessages/ViewMessages.kt @@ -1,18 +1,19 @@ package chatroom.viewmessages import chatroom.ChatroomWindow +import chatserver.ChatRepositories import org.koin.dsl.module val viewMessagesModule = module { scope { scoped { ViewMessagesPanel(get()) } - scoped { ViewMessagesUseCase(read = get()) } scoped { ViewMessagesPresenter( windowScope = get().windowScope, dispatchers = get(), - messages = get(), + read = get().readMessages(), + channel = get().subscribeMessages(), ) } } diff --git a/src/main/kotlin/chatroom/viewmessages/ViewMessagesPresenter.kt b/src/main/kotlin/chatroom/viewmessages/ViewMessagesPresenter.kt index 91b0a0d..5c5d9e0 100644 --- a/src/main/kotlin/chatroom/viewmessages/ViewMessagesPresenter.kt +++ b/src/main/kotlin/chatroom/viewmessages/ViewMessagesPresenter.kt @@ -4,28 +4,38 @@ import arch.Presenter import arch.RokyDispatchers import chatroom.viewmessages.ViewMessagesViewState.Messages import chatroom.viewmessages.ViewMessagesViewState.NoMessages +import chatserver.ChatMessageResult +import chatserver.ReadChatRepository +import chatserver.SubscribeChatRepository import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.withContext class ViewMessagesPresenter( private val windowScope: CoroutineScope, - private val messages: ViewMessagesUseCase, + private val read: ReadChatRepository, + private val channel: SubscribeChatRepository, dispatchers: RokyDispatchers, ) : Presenter(dispatchers) { override fun onAttach(view: ViewMessagesView) { view.show(NoMessages) windowScope.launch(dispatchers.io) { - messages().map { Messages(it) }.collect { message -> - withContext(dispatchers.main) { - withView { it.show(message) } + read.observe() + .filter { it.isOk } + .map { it.item } + .map(::Messages) + .collect { message -> + withContext(dispatchers.main) { + withView { it.show(message) } + } } - } } + channel.subscribe() } override fun onDetach(view: ViewMessagesView) { - // deliberately empty to please the auto-formatter + channel.unsubscribe() } } diff --git a/src/main/kotlin/chatroom/viewmessages/ViewMessagesUseCase.kt b/src/main/kotlin/chatroom/viewmessages/ViewMessagesUseCase.kt deleted file mode 100644 index d8061ca..0000000 --- a/src/main/kotlin/chatroom/viewmessages/ViewMessagesUseCase.kt +++ /dev/null @@ -1,66 +0,0 @@ -package chatroom.viewmessages - -import chatserver.MessagesRepository -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.merge -import kotlin.time.Duration.Companion.seconds - -class ViewMessagesUseCase( - private val messages: List = sampleMessages, - private val users: List = sampleUsers, - private val read: MessagesRepository.Read, -) { - operator fun invoke(): Flow { - val rnd = - flow { - while (true) { - delay(3.seconds) - emit("${users.random()}: ${messages.random()}") - } - } - return merge(rnd, read.observe().map { "Me: $it" }) - } - - companion object { - private val sampleMessages = - listOf( - "This is a coup!", - "What time's Roky Coding tonight?", - "Look at the calendar...", - "Charizard", - "Remind me to get my washing at 4 PM", - "ASMR....", - "BRAIN...praise me", - "Biggleswade is naff", - "Biggleswade is amazing /s", - "I love Biggleswade!!!", - "AHHHHHHHHHHHHHHHHHH", - "Wordle: 4/6", - "Wordle: 1/6", - "Wordle: 2/6", - "I AM SO HUNGRY RN", - "I am feeling quiet today", - ":thumbs_up:", - "I just have a bit of a cold rn", - "I just think it's something going around", - "Don't just type out what I'm saying Mike", - ":breathing_noises:", - ) - val sampleUsers = - listOf( - "Martine", - "Ed", - "Kai", - "Terry", - "Robert", - "Tom", - "Brian", - "Dunia", - "Stefano", - "Mike", - ) - } -} diff --git a/src/main/kotlin/chatserver/ChatRepositories.kt b/src/main/kotlin/chatserver/ChatRepositories.kt index be5bea8..df27581 100644 --- a/src/main/kotlin/chatserver/ChatRepositories.kt +++ b/src/main/kotlin/chatserver/ChatRepositories.kt @@ -14,4 +14,6 @@ class ChatRepositories( fun subscribeProfiles(): SubscribeChatRepository = profiles fun readMessages(): ReadChatRepository = messages + + fun subscribeMessages(): SubscribeChatRepository = messages } diff --git a/src/main/kotlin/chatserver/ChatServer.kt b/src/main/kotlin/chatserver/ChatServer.kt index cbe14a9..e8fcb6b 100644 --- a/src/main/kotlin/chatserver/ChatServer.kt +++ b/src/main/kotlin/chatserver/ChatServer.kt @@ -1,7 +1,5 @@ package chatserver -import chatserver.MessagesRepository.Read -import chatserver.MessagesRepository.Write import chatserver.messages.chatServerMessagesModule import chatserver.profiles.chatServerProfilesModule import org.koin.core.module.dsl.factoryOf @@ -11,6 +9,5 @@ import org.koin.dsl.module val chatServerModule = module { includes(chatServerProfilesModule, chatServerMessagesModule) - factory { MockMessages } binds arrayOf(Read::class, Write::class) factoryOf(::ChatRepositories) } diff --git a/src/main/kotlin/chatserver/MessagesRepository.kt b/src/main/kotlin/chatserver/MessagesRepository.kt deleted file mode 100644 index 85298a6..0000000 --- a/src/main/kotlin/chatserver/MessagesRepository.kt +++ /dev/null @@ -1,13 +0,0 @@ -package chatserver - -import kotlinx.coroutines.flow.Flow - -interface MessagesRepository { - interface Read { - fun observe(): Flow - } - - interface Write { - suspend fun send(message: String) - } -} diff --git a/src/main/kotlin/chatserver/MockMessages.kt b/src/main/kotlin/chatserver/MockMessages.kt deleted file mode 100644 index 88dede6..0000000 --- a/src/main/kotlin/chatserver/MockMessages.kt +++ /dev/null @@ -1,20 +0,0 @@ -package chatserver - -import chatserver.MessagesRepository.Read -import chatserver.MessagesRepository.Write -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.asSharedFlow - -object MockMessages : Read, Write { - private val _events = MutableSharedFlow(extraBufferCapacity = 64) - private val events = _events.asSharedFlow() - - override fun observe(): Flow { - return events - } - - override suspend fun send(message: String) { - _events.emit(message) - } -} diff --git a/src/main/kotlin/chatserver/messages/LocalChatMessages.kt b/src/main/kotlin/chatserver/messages/LocalChatMessages.kt index 71382d5..3d050bb 100644 --- a/src/main/kotlin/chatserver/messages/LocalChatMessages.kt +++ b/src/main/kotlin/chatserver/messages/LocalChatMessages.kt @@ -1,6 +1,7 @@ package chatserver.messages import arch.RokyDispatchers +import chatroom.users.UsersViewState import chatserver.ChatMessageResult import chatserver.ReadChatRepository import chatserver.SubscribeChatRepository @@ -16,6 +17,7 @@ class LocalChatMessages( private val scope: CoroutineScope = CoroutineScope(dispatchers.default + Job()), private val messages: List = sampleMessages, private val users: List = sampleUsers, + private val source: () -> Flow = {emitEveryThreeSeconds(users, messages)} ) : ReadChatRepository, SubscribeChatRepository { private var samples: Job? = null private val _events = MutableStateFlow("") @@ -28,12 +30,7 @@ class LocalChatMessages( override fun subscribe() { samples = scope.launch { - flow { - while (true) { - delay(3.seconds) - emit("${users.random()}: ${messages.random()}") - } - }.cancellable().collect { + source().cancellable().collect { _events.value = it } } @@ -84,5 +81,13 @@ class LocalChatMessages( "Stefano", "Mike", ) + + private fun emitEveryThreeSeconds(users:List, messages:List) = + flow { + while (true) { + delay(3.seconds) + emit("${users.random()}: ${messages.random()}") + } + } } } diff --git a/src/main/kotlin/chatserver/profiles/LocalProfilesRepository.kt b/src/main/kotlin/chatserver/profiles/LocalProfilesRepository.kt index a977863..759ad0b 100644 --- a/src/main/kotlin/chatserver/profiles/LocalProfilesRepository.kt +++ b/src/main/kotlin/chatserver/profiles/LocalProfilesRepository.kt @@ -1,12 +1,12 @@ package chatserver.profiles import arch.RokyDispatchers -import chatroom.viewmessages.ViewMessagesUseCase import chatserver.ProfileResult import chatserver.ProfileResult.Companion.ok import chatserver.ReadChatRepository import chatserver.SubscribeChatRepository import chatserver.WriteChatRepository +import chatserver.messages.LocalChatMessages import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow @@ -32,7 +32,7 @@ class LocalProfilesRepository( override fun subscribe() { scope.launch(dispatchers.default) { while (true) { - val users = ViewMessagesUseCase.sampleUsers.shuffled() + val users = LocalChatMessages.sampleUsers.shuffled() state.value = users.associateWith { it }.let(ProfileResult::ok) delay(5.seconds) } diff --git a/src/test/kotlin/chatroom/sendmessages/SendMessagePresenterTest.kt b/src/test/kotlin/chatroom/sendmessages/SendMessagePresenterTest.kt index e0e0b3d..cfd5b89 100644 --- a/src/test/kotlin/chatroom/sendmessages/SendMessagePresenterTest.kt +++ b/src/test/kotlin/chatroom/sendmessages/SendMessagePresenterTest.kt @@ -3,7 +3,7 @@ package chatroom.sendmessages import arch.RokyDispatchers import chatroom.sendmessages.SendMessageEvent.SendMessage import chatroom.sendmessages.SendMessageViewState.Clear -import chatserver.MessagesRepository +import io.kotest.matchers.collections.shouldContainExactly import io.mockk.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -16,14 +16,16 @@ import org.junit.jupiter.api.Test @OptIn(ExperimentalCoroutinesApi::class) class SendMessagePresenterTest { private lateinit var scope: CoroutineScope - private lateinit var send: MessagesRepository.Write + private lateinit var messages: MutableList private lateinit var view: SendMessagesView private lateinit var presenter: SendMessagePresenter @BeforeEach fun setUp() { - send = mockk() - coEvery { send.send(any()) } just runs + messages = mutableListOf() + val send:(String)->Unit = { + messages.add(it) + } view = mockk(relaxed = true) val dispatchers: RokyDispatchers = mockk().apply { @@ -46,7 +48,7 @@ class SendMessagePresenterTest { presenter.attach(view) presenter.onEvent(SendMessage("Hello")) advanceUntilIdle() - coVerify { send.send("Hello") } + messages.shouldContainExactly("Hello") } @Test diff --git a/src/test/kotlin/chatroom/viewmessages/ViewMessagesPresenterTest.kt b/src/test/kotlin/chatroom/viewmessages/ViewMessagesPresenterTest.kt index 15bf5c3..3acd1ec 100644 --- a/src/test/kotlin/chatroom/viewmessages/ViewMessagesPresenterTest.kt +++ b/src/test/kotlin/chatroom/viewmessages/ViewMessagesPresenterTest.kt @@ -3,10 +3,14 @@ package chatroom.viewmessages import arch.RokyDispatchers import chatroom.viewmessages.ViewMessagesViewState.Messages import chatroom.viewmessages.ViewMessagesViewState.NoMessages +import chatserver.ChatMessageResult +import chatserver.ReadChatRepository +import chatserver.SubscribeChatRepository import coAnswersDelayed import io.mockk.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.advanceUntilIdle @@ -14,19 +18,22 @@ import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import kotlin.test.assertEquals +import kotlin.test.assertFalse import kotlin.test.assertTrue @OptIn(ExperimentalCoroutinesApi::class) class ViewMessagesPresenterTest { - private lateinit var messages: ViewMessagesUseCase + private lateinit var channel: SubscribeChatRepository + private lateinit var read: ReadChatRepository private lateinit var scope: CoroutineScope private lateinit var view: ViewMessagesView private lateinit var presenter: ViewMessagesPresenter @BeforeEach fun setUp() { - messages = mockk(relaxed = true) - coEvery { messages() } coAnswersDelayed { flowOf() } + channel = mockk(relaxed = true) + read = mockk() + every { read.observe() } returns flowOf() view = mockk(relaxed = true) view = mockk(relaxed = true) val dispatchers: RokyDispatchers = @@ -35,7 +42,7 @@ class ViewMessagesPresenterTest { every { io } returns dispatcher } scope = CoroutineScope(dispatcher) - presenter = ViewMessagesPresenter(scope, messages, dispatchers) + presenter = ViewMessagesPresenter(scope, read, channel, dispatchers) } @Test @@ -50,30 +57,57 @@ class ViewMessagesPresenterTest { @Test fun `given message exist, when attached, then show message`() = runTest(dispatcher) { - coEvery { messages() } coAnswersDelayed { flowOf("Biggleswade is beautiful") } + every { read.observe() } returns flowOf(ChatMessageResult.ok("Biggleswade is bad")) presenter.attach(view) advanceUntilIdle() verifyOrder { view.show(NoMessages) - view.show(assertMessage("Biggleswade is beautiful")) + view.show(assertMessage("Biggleswade is bad")) } } + @Test + fun `given message exist, and message is not ok, when attached, then show nothing`() = + runTest(dispatcher) { + every { read.observe() } returns flowOf(ChatMessageResult.fail(RuntimeException ("Biggleswade is bad"))) + presenter.attach(view) + advanceUntilIdle() + verifyOrder { + view.show(NoMessages) + } + verify(exactly = 0) { view.show(withArg { + assertFalse{ it !is Messages } + }) } + } + + @Test + fun `when attached, then subscribe to chat messages`() = runTest(dispatcher){ + presenter.attach(view) + verify { channel.subscribe() } + } + + @Test + fun `when detached, then unsubscribe to chat messages`() = runTest(dispatcher){ + presenter.attach(view) + presenter.detach() + verify { channel.unsubscribe() } + } + @Test fun `given two messages exist, when attached, then show two messages`() = runTest(dispatcher) { - coEvery { messages() } coAnswersDelayed { + every { read.observe() } returns flowOf( - "Biggleswade is beautiful", - "Robert is good at Kotlin", + ChatMessageResult.ok("Biggleswade is bad"), + ChatMessageResult.ok("Robert is good at bad, kai is better"), ) - } + presenter.attach(view) advanceUntilIdle() verifyOrder { view.show(NoMessages) - view.show(assertMessage("Biggleswade is beautiful")) - view.show(assertMessage("Robert is good at Kotlin")) + view.show(assertMessage("Biggleswade is bad")) + view.show(assertMessage("Robert is good at bad, kai is better")) } } diff --git a/src/test/kotlin/chatroom/viewmessages/ViewMessagesUseCaseTest.kt b/src/test/kotlin/chatroom/viewmessages/ViewMessagesUseCaseTest.kt index b1068d1..036b444 100644 --- a/src/test/kotlin/chatroom/viewmessages/ViewMessagesUseCaseTest.kt +++ b/src/test/kotlin/chatroom/viewmessages/ViewMessagesUseCaseTest.kt @@ -1,69 +1,68 @@ -package chatroom.viewmessages - -import chatserver.MessagesRepository -import io.mockk.every -import io.mockk.mockk -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.launch -import kotlinx.coroutines.test.advanceTimeBy -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import kotlin.test.assertContains -import kotlin.test.assertEquals -import kotlin.time.Duration.Companion.seconds - -@OptIn(ExperimentalCoroutinesApi::class) -class ViewMessagesUseCaseTest { - private lateinit var messages: MessagesRepository.Read - private lateinit var viewMessages: ViewMessagesUseCase - - @BeforeEach - fun setUp() { - messages = mockk() - every { - messages.observe() - } returns flowOf() - } - - @Test - fun `given messages exist, when observing messages, then receive messages`() = - runTest { - viewMessages = ViewMessagesUseCase(listOf("Rob is a goodie twoshoes!!"), listOf("Brian"), messages) - assertEquals( - expected = "Brian: Rob is a goodie twoshoes!!", - actual = viewMessages().first(), - ) - } - - @Test - fun `given messages exist, when observing multiple messages, then receive multiple messages`() = - runTest { - viewMessages = ViewMessagesUseCase(listOf("Rob is a goodie twoshoes!!"), listOf("Brian"), messages) - val emissions = mutableListOf() - backgroundScope.launch { viewMessages().collect(emissions::add) } - advanceTimeBy(10.seconds) - assertEquals( - expected = 3, - actual = emissions.size, - ) - } - - @Test - fun `given messages exist, when messages interleave, then print all messages`() = - runTest { - every { - messages.observe() - } returns flowOf("Hi there you alright?!") - viewMessages = ViewMessagesUseCase(listOf("Rob is a goodie twoshoes!!"), listOf("Brian"), messages) - val emissions = mutableListOf() - backgroundScope.launch { - viewMessages().collect(emissions::add) - } - advanceTimeBy(10.seconds) - assertEquals(4, emissions.size) - assertContains(emissions, "Me: Hi there you alright?!") - } -} +//package chatroom.viewmessages +// +//import io.mockk.every +//import io.mockk.mockk +//import kotlinx.coroutines.ExperimentalCoroutinesApi +//import kotlinx.coroutines.flow.first +//import kotlinx.coroutines.flow.flowOf +//import kotlinx.coroutines.launch +//import kotlinx.coroutines.test.advanceTimeBy +//import kotlinx.coroutines.test.runTest +//import org.junit.jupiter.api.BeforeEach +//import org.junit.jupiter.api.Test +//import kotlin.test.assertContains +//import kotlin.test.assertEquals +//import kotlin.time.Duration.Companion.seconds +// +//@OptIn(ExperimentalCoroutinesApi::class) +//class ViewMessagesUseCaseTest { +// private lateinit var messages: MessagesRepository.Read +// private lateinit var viewMessages: ViewMessagesUseCase +// +// @BeforeEach +// fun setUp() { +// messages = mockk() +// every { +// messages.observe() +// } returns flowOf() +// } +// +// @Test +// fun `given messages exist, when observing messages, then receive messages`() = +// runTest { +// viewMessages = ViewMessagesUseCase(listOf("Rob is a goodie twoshoes!!"), listOf("Brian"), messages) +// assertEquals( +// expected = "Brian: Rob is a goodie twoshoes!!", +// actual = viewMessages().first(), +// ) +// } +// +// @Test +// fun `given messages exist, when observing multiple messages, then receive multiple messages`() = +// runTest { +// viewMessages = ViewMessagesUseCase(listOf("Rob is a goodie twoshoes!!"), listOf("Brian"), messages) +// val emissions = mutableListOf() +// backgroundScope.launch { viewMessages().collect(emissions::add) } +// advanceTimeBy(10.seconds) +// assertEquals( +// expected = 3, +// actual = emissions.size, +// ) +// } +// +// @Test +// fun `given messages exist, when messages interleave, then print all messages`() = +// runTest { +// every { +// messages.observe() +// } returns flowOf("Hi there you alright?!") +// viewMessages = ViewMessagesUseCase(listOf("Rob is a goodie twoshoes!!"), listOf("Brian"), messages) +// val emissions = mutableListOf() +// backgroundScope.launch { +// viewMessages().collect(emissions::add) +// } +// advanceTimeBy(10.seconds) +// assertEquals(4, emissions.size) +// assertContains(emissions, "Me: Hi there you alright?!") +// } +//} diff --git a/src/test/kotlin/chatserver/messages/LocalChatMessagesTest.kt b/src/test/kotlin/chatserver/messages/LocalChatMessagesTest.kt new file mode 100644 index 0000000..76efeff --- /dev/null +++ b/src/test/kotlin/chatserver/messages/LocalChatMessagesTest.kt @@ -0,0 +1,116 @@ +package chatserver.messages + +import arch.RokyDispatchers +import io.kotest.inspectors.shouldForAll +import io.kotest.matchers.collections.shouldContain +import io.kotest.matchers.collections.shouldContainInOrder +import io.kotest.matchers.collections.shouldMatchEach +import io.kotest.matchers.string.shouldStartWith +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import kotlin.test.Test +import kotlin.time.Duration.Companion.seconds +@OptIn(ExperimentalCoroutinesApi::class) +class LocalChatMessagesTest { + private lateinit var scope: CoroutineScope + private lateinit var messages: LocalChatMessages + + + @BeforeEach + fun setUp() { + + val dispatchers: RokyDispatchers = + mockk().apply { + every { main } returns dispatcher + every { io } returns dispatcher + } + scope = CoroutineScope(dispatcher) + messages = LocalChatMessages(dispatchers, scope) + } + + + @Test + fun `given messages exist, when observing messages, then receive messages`() = + runTest(dispatcher) { + messages = LocalChatMessages(dispatchers, scope, listOf("Rob is bad!!"), listOf("Kai"), ) + backgroundScope.launch { messages.subscribe()} + dispatcher.scheduler.advanceTimeBy(10.seconds) + + assertEquals("Kai: Rob is bad!!", messages.latest().item ) + + messages.unsubscribe() + } + + @Test + fun `when observing multiple messages, then receive same number of messages`() = + runTest(dispatcher) { + messages = LocalChatMessages(dispatchers, scope, listOf("Rob is bad!!", "Kai is good!"), listOf("Kai"), ) + val emissions = mutableListOf() + backgroundScope.launch { + messages.subscribe() + messages.observe().map { it.item }.collect(emissions::add) + } + + dispatcher.scheduler.advanceTimeBy(10.seconds) + messages.unsubscribe() + + assertEquals(2, emissions.size) + + + } + + @Test + fun `when observing multiple messages, then receive multiple messages`() = + runTest(dispatcher) { + val content = listOf("Rob is bad!!", "Kai is good!") + messages = LocalChatMessages(dispatchers, scope, content, listOf("Kai"), {emitImmediately("Kai", content)} ) + val emissions = mutableListOf() + backgroundScope.launch { + messages.observe().map { it.item }.collect(emissions::add) + messages.subscribe() + } + + dispatcher.scheduler.advanceTimeBy(10.seconds) + + + + emissions.shouldContainInOrder( + "", + "Kai: Rob is bad!!", + "Kai: Kai is good!" + ) + + messages.unsubscribe() + + + } + + + companion object { + private val dispatcher = StandardTestDispatcher() + private val dispatchers: RokyDispatchers + get() = mockk().apply { + every { main } returns dispatcher + every { io } returns dispatcher + } + private fun emitImmediately(user:String, messages:List ) = flow { + messages.map {"$user: $it"} .forEach{emit(it)} + + } + + } + +} From c6677340868f63bac4746122835b1e350b45077f Mon Sep 17 00:00:00 2001 From: Tom Calver Date: Thu, 29 May 2025 21:45:42 +0100 Subject: [PATCH 3/3] #156: Complete unit tests for local chat messages --- .../sendmessages/SendMessagePresenter.kt | 2 +- src/main/kotlin/chatserver/ChatServer.kt | 1 - .../chatserver/messages/LocalChatMessages.kt | 19 +++-- .../sendmessages/SendMessagePresenterTest.kt | 2 +- .../viewmessages/ViewMessagesPresenterTest.kt | 34 ++++---- .../viewmessages/ViewMessagesUseCaseTest.kt | 68 ---------------- .../messages/LocalChatMessagesTest.kt | 78 ++++++++----------- 7 files changed, 62 insertions(+), 142 deletions(-) delete mode 100644 src/test/kotlin/chatroom/viewmessages/ViewMessagesUseCaseTest.kt diff --git a/src/main/kotlin/chatroom/sendmessages/SendMessagePresenter.kt b/src/main/kotlin/chatroom/sendmessages/SendMessagePresenter.kt index f826435..80942e7 100644 --- a/src/main/kotlin/chatroom/sendmessages/SendMessagePresenter.kt +++ b/src/main/kotlin/chatroom/sendmessages/SendMessagePresenter.kt @@ -10,7 +10,7 @@ import kotlinx.coroutines.withContext class SendMessagePresenter( private val windowScope: CoroutineScope, - private val send:(String)->Unit = ::println, + private val send: (String) -> Unit = ::println, dispatchers: RokyDispatchers, ) : Presenter(dispatchers) { override fun onAttach(view: SendMessagesView) { diff --git a/src/main/kotlin/chatserver/ChatServer.kt b/src/main/kotlin/chatserver/ChatServer.kt index e8fcb6b..f649021 100644 --- a/src/main/kotlin/chatserver/ChatServer.kt +++ b/src/main/kotlin/chatserver/ChatServer.kt @@ -3,7 +3,6 @@ package chatserver import chatserver.messages.chatServerMessagesModule import chatserver.profiles.chatServerProfilesModule import org.koin.core.module.dsl.factoryOf -import org.koin.dsl.binds import org.koin.dsl.module val chatServerModule = diff --git a/src/main/kotlin/chatserver/messages/LocalChatMessages.kt b/src/main/kotlin/chatserver/messages/LocalChatMessages.kt index 3d050bb..d8bf52c 100644 --- a/src/main/kotlin/chatserver/messages/LocalChatMessages.kt +++ b/src/main/kotlin/chatserver/messages/LocalChatMessages.kt @@ -1,7 +1,6 @@ package chatserver.messages import arch.RokyDispatchers -import chatroom.users.UsersViewState import chatserver.ChatMessageResult import chatserver.ReadChatRepository import chatserver.SubscribeChatRepository @@ -15,9 +14,7 @@ import kotlin.time.Duration.Companion.seconds class LocalChatMessages( private val dispatchers: RokyDispatchers, private val scope: CoroutineScope = CoroutineScope(dispatchers.default + Job()), - private val messages: List = sampleMessages, - private val users: List = sampleUsers, - private val source: () -> Flow = {emitEveryThreeSeconds(users, messages)} + private val source: () -> Flow = { emitEveryThreeSeconds(sampleUsers, sampleMessages) }, ) : ReadChatRepository, SubscribeChatRepository { private var samples: Job? = null private val _events = MutableStateFlow("") @@ -82,12 +79,14 @@ class LocalChatMessages( "Mike", ) - private fun emitEveryThreeSeconds(users:List, messages:List) = - flow { - while (true) { - delay(3.seconds) - emit("${users.random()}: ${messages.random()}") - } + private fun emitEveryThreeSeconds( + users: List, + messages: List, + ) = flow { + while (true) { + delay(3.seconds) + emit("${users.random()}: ${messages.random()}") } + } } } diff --git a/src/test/kotlin/chatroom/sendmessages/SendMessagePresenterTest.kt b/src/test/kotlin/chatroom/sendmessages/SendMessagePresenterTest.kt index cfd5b89..90a994c 100644 --- a/src/test/kotlin/chatroom/sendmessages/SendMessagePresenterTest.kt +++ b/src/test/kotlin/chatroom/sendmessages/SendMessagePresenterTest.kt @@ -23,7 +23,7 @@ class SendMessagePresenterTest { @BeforeEach fun setUp() { messages = mutableListOf() - val send:(String)->Unit = { + val send: (String) -> Unit = { messages.add(it) } view = mockk(relaxed = true) diff --git a/src/test/kotlin/chatroom/viewmessages/ViewMessagesPresenterTest.kt b/src/test/kotlin/chatroom/viewmessages/ViewMessagesPresenterTest.kt index 3acd1ec..ad6d2fb 100644 --- a/src/test/kotlin/chatroom/viewmessages/ViewMessagesPresenterTest.kt +++ b/src/test/kotlin/chatroom/viewmessages/ViewMessagesPresenterTest.kt @@ -6,11 +6,9 @@ import chatroom.viewmessages.ViewMessagesViewState.NoMessages import chatserver.ChatMessageResult import chatserver.ReadChatRepository import chatserver.SubscribeChatRepository -import coAnswersDelayed import io.mockk.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.advanceUntilIdle @@ -69,29 +67,35 @@ class ViewMessagesPresenterTest { @Test fun `given message exist, and message is not ok, when attached, then show nothing`() = runTest(dispatcher) { - every { read.observe() } returns flowOf(ChatMessageResult.fail(RuntimeException ("Biggleswade is bad"))) + every { read.observe() } returns flowOf(ChatMessageResult.fail(RuntimeException("Biggleswade is bad"))) presenter.attach(view) advanceUntilIdle() verifyOrder { view.show(NoMessages) } - verify(exactly = 0) { view.show(withArg { - assertFalse{ it !is Messages } - }) } + verify(exactly = 0) { + view.show( + withArg { + assertFalse { it !is Messages } + }, + ) + } } @Test - fun `when attached, then subscribe to chat messages`() = runTest(dispatcher){ - presenter.attach(view) - verify { channel.subscribe() } - } + fun `when attached, then subscribe to chat messages`() = + runTest(dispatcher) { + presenter.attach(view) + verify { channel.subscribe() } + } @Test - fun `when detached, then unsubscribe to chat messages`() = runTest(dispatcher){ - presenter.attach(view) - presenter.detach() - verify { channel.unsubscribe() } - } + fun `when detached, then unsubscribe to chat messages`() = + runTest(dispatcher) { + presenter.attach(view) + presenter.detach() + verify { channel.unsubscribe() } + } @Test fun `given two messages exist, when attached, then show two messages`() = diff --git a/src/test/kotlin/chatroom/viewmessages/ViewMessagesUseCaseTest.kt b/src/test/kotlin/chatroom/viewmessages/ViewMessagesUseCaseTest.kt deleted file mode 100644 index 036b444..0000000 --- a/src/test/kotlin/chatroom/viewmessages/ViewMessagesUseCaseTest.kt +++ /dev/null @@ -1,68 +0,0 @@ -//package chatroom.viewmessages -// -//import io.mockk.every -//import io.mockk.mockk -//import kotlinx.coroutines.ExperimentalCoroutinesApi -//import kotlinx.coroutines.flow.first -//import kotlinx.coroutines.flow.flowOf -//import kotlinx.coroutines.launch -//import kotlinx.coroutines.test.advanceTimeBy -//import kotlinx.coroutines.test.runTest -//import org.junit.jupiter.api.BeforeEach -//import org.junit.jupiter.api.Test -//import kotlin.test.assertContains -//import kotlin.test.assertEquals -//import kotlin.time.Duration.Companion.seconds -// -//@OptIn(ExperimentalCoroutinesApi::class) -//class ViewMessagesUseCaseTest { -// private lateinit var messages: MessagesRepository.Read -// private lateinit var viewMessages: ViewMessagesUseCase -// -// @BeforeEach -// fun setUp() { -// messages = mockk() -// every { -// messages.observe() -// } returns flowOf() -// } -// -// @Test -// fun `given messages exist, when observing messages, then receive messages`() = -// runTest { -// viewMessages = ViewMessagesUseCase(listOf("Rob is a goodie twoshoes!!"), listOf("Brian"), messages) -// assertEquals( -// expected = "Brian: Rob is a goodie twoshoes!!", -// actual = viewMessages().first(), -// ) -// } -// -// @Test -// fun `given messages exist, when observing multiple messages, then receive multiple messages`() = -// runTest { -// viewMessages = ViewMessagesUseCase(listOf("Rob is a goodie twoshoes!!"), listOf("Brian"), messages) -// val emissions = mutableListOf() -// backgroundScope.launch { viewMessages().collect(emissions::add) } -// advanceTimeBy(10.seconds) -// assertEquals( -// expected = 3, -// actual = emissions.size, -// ) -// } -// -// @Test -// fun `given messages exist, when messages interleave, then print all messages`() = -// runTest { -// every { -// messages.observe() -// } returns flowOf("Hi there you alright?!") -// viewMessages = ViewMessagesUseCase(listOf("Rob is a goodie twoshoes!!"), listOf("Brian"), messages) -// val emissions = mutableListOf() -// backgroundScope.launch { -// viewMessages().collect(emissions::add) -// } -// advanceTimeBy(10.seconds) -// assertEquals(4, emissions.size) -// assertContains(emissions, "Me: Hi there you alright?!") -// } -//} diff --git a/src/test/kotlin/chatserver/messages/LocalChatMessagesTest.kt b/src/test/kotlin/chatserver/messages/LocalChatMessagesTest.kt index 76efeff..62a9df3 100644 --- a/src/test/kotlin/chatserver/messages/LocalChatMessagesTest.kt +++ b/src/test/kotlin/chatserver/messages/LocalChatMessagesTest.kt @@ -1,37 +1,32 @@ package chatserver.messages import arch.RokyDispatchers -import io.kotest.inspectors.shouldForAll -import io.kotest.matchers.collections.shouldContain import io.kotest.matchers.collections.shouldContainInOrder -import io.kotest.matchers.collections.shouldMatchEach -import io.kotest.matchers.string.shouldStartWith +import io.kotest.matchers.collections.shouldHaveSize import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach import kotlin.test.Test import kotlin.time.Duration.Companion.seconds + @OptIn(ExperimentalCoroutinesApi::class) class LocalChatMessagesTest { private lateinit var scope: CoroutineScope private lateinit var messages: LocalChatMessages - @BeforeEach fun setUp() { - val dispatchers: RokyDispatchers = mockk().apply { every { main } returns dispatcher @@ -41,15 +36,14 @@ class LocalChatMessagesTest { messages = LocalChatMessages(dispatchers, scope) } - @Test fun `given messages exist, when observing messages, then receive messages`() = runTest(dispatcher) { - messages = LocalChatMessages(dispatchers, scope, listOf("Rob is bad!!"), listOf("Kai"), ) - backgroundScope.launch { messages.subscribe()} + messages = LocalChatMessages(dispatchers, scope, kaimitter()) + backgroundScope.launch { messages.subscribe() } dispatcher.scheduler.advanceTimeBy(10.seconds) - assertEquals("Kai: Rob is bad!!", messages.latest().item ) + assertEquals(MSG_2, messages.latest().item) messages.unsubscribe() } @@ -57,60 +51,52 @@ class LocalChatMessagesTest { @Test fun `when observing multiple messages, then receive same number of messages`() = runTest(dispatcher) { - messages = LocalChatMessages(dispatchers, scope, listOf("Rob is bad!!", "Kai is good!"), listOf("Kai"), ) + messages = LocalChatMessages(dispatchers, scope, kaimitter()) + val emissions = mutableListOf() backgroundScope.launch { - messages.subscribe() messages.observe().map { it.item }.collect(emissions::add) } + messages.subscribe() - dispatcher.scheduler.advanceTimeBy(10.seconds) - messages.unsubscribe() - - assertEquals(2, emissions.size) - + advanceTimeBy(10.seconds) + emissions.shouldHaveSize(3) + messages.unsubscribe() } @Test fun `when observing multiple messages, then receive multiple messages`() = runTest(dispatcher) { - val content = listOf("Rob is bad!!", "Kai is good!") - messages = LocalChatMessages(dispatchers, scope, content, listOf("Kai"), {emitImmediately("Kai", content)} ) + messages = LocalChatMessages(dispatchers, scope, kaimitter()) + val emissions = mutableListOf() backgroundScope.launch { messages.observe().map { it.item }.collect(emissions::add) - messages.subscribe() } + messages.subscribe() - dispatcher.scheduler.advanceTimeBy(10.seconds) - - - - emissions.shouldContainInOrder( - "", - "Kai: Rob is bad!!", - "Kai: Kai is good!" - ) + advanceTimeBy(10.seconds) + emissions.shouldContainInOrder("", MSG_1, MSG_2) messages.unsubscribe() - - } - companion object { private val dispatcher = StandardTestDispatcher() private val dispatchers: RokyDispatchers - get() = mockk().apply { - every { main } returns dispatcher - every { io } returns dispatcher - } - private fun emitImmediately(user:String, messages:List ) = flow { - messages.map {"$user: $it"} .forEach{emit(it)} - - } - + get() = + mockk().apply { + every { main } returns dispatcher + every { io } returns dispatcher + } + + private const val USR = "Kai" + private const val CONTENT_1 = "Rob is bad!!" + private const val CONTENT_2 = "Kai is worse!" + private const val MSG_1 = "$USR: $CONTENT_1" + private const val MSG_2 = "$USR: $CONTENT_2" + + private fun kaimitter() = { flowOf(MSG_1, MSG_2).onEach { delay(1.seconds) } } } - }