diff --git a/mirai-core/src/commonMain/kotlin/network/handler/NetworkHandler.kt b/mirai-core/src/commonMain/kotlin/network/handler/NetworkHandler.kt
index 33f173ae9..aae54607e 100644
--- a/mirai-core/src/commonMain/kotlin/network/handler/NetworkHandler.kt
+++ b/mirai-core/src/commonMain/kotlin/network/handler/NetworkHandler.kt
@@ -27,7 +27,8 @@ import java.util.concurrent.CancellationException
  * Immutable context for [NetworkHandler]
  */
 internal interface NetworkHandlerContext {
-    val bot: QQAndroidBot
+    val bot: QQAndroidBot // // TODO: 2021/4/16 this is bad, making it difficult to write unit tests.
+    // however migration requires a major change.
 
     val logger: MiraiLogger
     val ssoContext: SsoContext
diff --git a/mirai-core/src/commonMain/kotlin/network/handler/SelectorNetworkHandler.kt b/mirai-core/src/commonMain/kotlin/network/handler/SelectorNetworkHandler.kt
index e6e4dffff..890df70b6 100644
--- a/mirai-core/src/commonMain/kotlin/network/handler/SelectorNetworkHandler.kt
+++ b/mirai-core/src/commonMain/kotlin/network/handler/SelectorNetworkHandler.kt
@@ -10,7 +10,9 @@
 package net.mamoe.mirai.internal.network.handler
 
 import kotlinx.atomicfu.atomic
+import net.mamoe.mirai.internal.network.handler.NetworkHandler.State
 import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
+import org.jetbrains.annotations.TestOnly
 
 /**
  * A proxy to [NetworkHandler] that delegates calls to instance returned by [NetworkHandlerSelector.awaitResumeInstance].
@@ -26,8 +28,8 @@ internal class SelectorNetworkHandler(
 ) : NetworkHandler {
     private suspend inline fun instance(): NetworkHandler = selector.awaitResumeInstance()
 
-    override val state: NetworkHandler.State
-        get() = selector.getResumedInstance()?.state ?: NetworkHandler.State.INITIALIZED
+    override val state: State
+        get() = selector.getResumedInstance()?.state ?: State.INITIALIZED
 
     override suspend fun resumeConnection() {
         instance() // the selector will resume connection for us.
@@ -80,6 +82,11 @@ internal interface NetworkHandlerSelector<H : NetworkHandler> {
 internal abstract class AbstractKeepAliveNetworkHandlerSelector<H : NetworkHandler> : NetworkHandlerSelector<H> {
     private val current = atomic<H?>(null)
 
+    @TestOnly
+    internal fun setCurrent(h: H) {
+        current.value = h
+    }
+
     protected abstract fun createInstance(): H
 
     final override fun getResumedInstance(): H? = current.value
@@ -89,14 +96,16 @@ internal abstract class AbstractKeepAliveNetworkHandlerSelector<H : NetworkHandl
         val current = getResumedInstance()
         return if (current != null) {
             when (current.state) {
-                NetworkHandler.State.OK -> current
-                NetworkHandler.State.CLOSED -> {
+                State.CLOSED -> {
                     this.current.compareAndSet(current, null) // invalidate the instance and try again.
-                    awaitResumeInstance()
+                    awaitResumeInstance() // will create new instance.
                 }
-                else -> {
-                    current.resumeConnection() // try to advance state.
-                    awaitResumeInstance()
+                State.CONNECTING,
+                State.CONNECTION_LOST,
+                State.INITIALIZED,
+                State.OK -> {
+                    current.resumeConnection()
+                    return current
                 }
             }
         } else {
diff --git a/mirai-core/src/commonMain/kotlin/network/handler/ServerList.kt b/mirai-core/src/commonMain/kotlin/network/handler/ServerList.kt
index 1cffc180b..b78757616 100644
--- a/mirai-core/src/commonMain/kotlin/network/handler/ServerList.kt
+++ b/mirai-core/src/commonMain/kotlin/network/handler/ServerList.kt
@@ -77,7 +77,8 @@ internal class ServerList(
                 |157.255.13.77:14000, 120.232.18.27:443, 
                 |183.3.235.162:14000, 163.177.89.195:443, 183.232.94.44:80, 
                 |203.205.255.224:8080, 203.205.255.221:8080""".trimMargin()
-                .split(", ", "\n").filterNot(String::isBlank)
+                .splitToSequence(",").filterNot(String::isBlank)
+                .map { it.trim() }
                 .map {
                     val host = it.substringBefore(':')
                     val port = it.substringAfter(':').toInt()
diff --git a/mirai-core/src/commonMain/kotlin/network/net/protocol/SsoController.kt b/mirai-core/src/commonMain/kotlin/network/net/protocol/SsoController.kt
index fba8c9758..23e8bdccf 100644
--- a/mirai-core/src/commonMain/kotlin/network/net/protocol/SsoController.kt
+++ b/mirai-core/src/commonMain/kotlin/network/net/protocol/SsoController.kt
@@ -25,15 +25,15 @@ import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
 import net.mamoe.mirai.internal.utils.crypto.TEA
 import net.mamoe.mirai.internal.utils.io.serialization.loadAs
 import net.mamoe.mirai.network.*
+import net.mamoe.mirai.utils.*
 import net.mamoe.mirai.utils.BotConfiguration.MiraiProtocol
-import net.mamoe.mirai.utils.DeviceInfo
-import net.mamoe.mirai.utils.LoginSolver
-import net.mamoe.mirai.utils.info
-import net.mamoe.mirai.utils.withExceptionCollector
 import java.io.File
 
 internal interface SsoContext {
     var client: QQAndroidClient
+    val configuration: BotConfiguration
+    val loginSessionAware: LoginSessionAware get() = client
+    val accountSecrets: AccountSecrets get() = client
 }
 
 internal class SsoController(
@@ -42,7 +42,7 @@ internal class SsoController(
 ) {
     @Throws(LoginFailedException::class)
     suspend fun login() = withExceptionCollector {
-        if (bot.client.wLoginSigInfoInitialized) {
+        if (ssoContext.accountSecrets.wLoginSigInfoInitialized) {
             kotlin.runCatching {
                 fastLogin()
             }.onFailure { e ->
@@ -181,6 +181,7 @@ internal class SsoController(
 
     }
 
+    @Suppress("unused") // false positive
     internal fun initClient() {
         val device = configuration.deviceInfo?.invoke(bot) ?: DeviceInfo.random()
         ssoContext.client = QQAndroidClient(
@@ -188,7 +189,7 @@ internal class SsoController(
             device = device,
             accountSecrets = loadSecretsFromCacheOrCreate(device)
         ).apply {
-            _bot = bot
+            _bot = this@SsoController.bot
         }
     }
 
@@ -201,7 +202,7 @@ internal class SsoController(
     // TODO: 2021/4/14 extract a cache service
 
     private val cacheDir: File by lazy {
-        configuration.workingDir.resolve(bot.configuration.cacheDir).apply { mkdirs() }
+        configuration.workingDir.resolve(ssoContext.configuration.cacheDir).apply { mkdirs() }
     }
     private val accountSecretsFile: File by lazy {
         cacheDir.resolve("account.secrets")
diff --git a/mirai-core/src/commonTest/kotlin/network/handler/AbstractKeepAliveNetworkHandlerSelectorTest.kt b/mirai-core/src/commonTest/kotlin/network/handler/AbstractKeepAliveNetworkHandlerSelectorTest.kt
new file mode 100644
index 000000000..f288b0409
--- /dev/null
+++ b/mirai-core/src/commonTest/kotlin/network/handler/AbstractKeepAliveNetworkHandlerSelectorTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2019-2021 Mamoe Technologies and contributors.
+ *
+ *  此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ *  Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ *
+ *  https://github.com/mamoe/mirai/blob/master/LICENSE
+ */
+
+package net.mamoe.mirai.internal.network.handler
+
+import net.mamoe.mirai.internal.network.handler.NetworkHandler.State
+import net.mamoe.mirai.internal.test.runBlockingUnit
+import java.util.concurrent.atomic.AtomicInteger
+import kotlin.test.*
+import kotlin.time.seconds
+
+private class TestSelector(val createInstance0: () -> NetworkHandler) :
+    AbstractKeepAliveNetworkHandlerSelector<NetworkHandler>() {
+    val createInstanceCount = AtomicInteger(0)
+    override fun createInstance(): NetworkHandler {
+        createInstanceCount.incrementAndGet()
+        return this.createInstance0()
+    }
+}
+
+internal class AbstractKeepAliveNetworkHandlerSelectorTest {
+
+    private fun createHandler() = TestNetworkHandler(TestNetworkHandlerContext())
+
+    @Test
+    fun `can initialize instance`() {
+        val selector = TestSelector { createHandler() }
+        runBlockingUnit(timeout = 3.seconds) { selector.awaitResumeInstance() }
+        assertNotNull(selector.getResumedInstance())
+    }
+
+    @Test
+    fun `no redundant initialization`() {
+        val selector = TestSelector {
+            fail("initialize called")
+        }
+        val handler = createHandler()
+        selector.setCurrent(handler)
+        assertSame(handler, selector.getResumedInstance())
+    }
+
+    @Test
+    fun `initialize another when closed`() {
+        val selector = TestSelector {
+            createHandler()
+        }
+        val handler = createHandler()
+        selector.setCurrent(handler)
+        assertSame(handler, selector.getResumedInstance())
+        handler.setState(State.CLOSED)
+        runBlockingUnit(timeout = 3.seconds) { selector.awaitResumeInstance() }
+        assertEquals(1, selector.createInstanceCount.get())
+    }
+}
\ No newline at end of file
diff --git a/mirai-core/src/commonTest/kotlin/network/handler/testUtils.kt b/mirai-core/src/commonTest/kotlin/network/handler/testUtils.kt
new file mode 100644
index 000000000..970574074
--- /dev/null
+++ b/mirai-core/src/commonTest/kotlin/network/handler/testUtils.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2019-2021 Mamoe Technologies and contributors.
+ *
+ *  此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ *  Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ *
+ *  https://github.com/mamoe/mirai/blob/master/LICENSE
+ */
+
+package net.mamoe.mirai.internal.network.handler
+
+import kotlinx.coroutines.CompletableDeferred
+import net.mamoe.mirai.internal.MockBot
+import net.mamoe.mirai.internal.QQAndroidBot
+import net.mamoe.mirai.internal.network.handler.impl.NetworkHandlerSupport
+import net.mamoe.mirai.internal.network.net.protocol.SsoContext
+import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
+import net.mamoe.mirai.utils.BotConfiguration
+import net.mamoe.mirai.utils.MiraiLogger
+import java.util.concurrent.ConcurrentLinkedQueue
+import java.util.concurrent.atomic.AtomicInteger
+
+
+internal class TestNetworkHandlerContext(
+    override val bot: QQAndroidBot = MockBot(),
+    override val logger: MiraiLogger = MiraiLogger.create("Test"),
+    override val ssoContext: SsoContext = bot,
+    override val configuration: BotConfiguration = bot.configuration
+) : NetworkHandlerContext
+
+internal open class TestNetworkHandler(
+    context: NetworkHandlerContext,
+) : NetworkHandlerSupport(context) {
+    @Suppress("EXPOSED_SUPER_CLASS")
+    internal open inner class TestState(
+        correspondingState: NetworkHandler.State
+    ) : BaseStateImpl(correspondingState) {
+        val resumeDeferred = CompletableDeferred<Unit>()
+        val resumeCount = AtomicInteger(0)
+        val onResume get() = resumeDeferred.onJoin
+
+        override suspend fun resumeConnection() {
+            resumeCount.incrementAndGet()
+            resumeDeferred.complete(Unit)
+        }
+    }
+
+    fun setState(correspondingState: NetworkHandler.State) {
+        setState { TestState(correspondingState) }
+    }
+
+    private val initialState = TestState(NetworkHandler.State.INITIALIZED)
+    override fun initialState(): BaseStateImpl = initialState
+
+    val sendPacket get() = ConcurrentLinkedQueue<OutgoingPacket>()
+
+    override suspend fun sendPacketImpl(packet: OutgoingPacket) {
+        sendPacket.add(packet)
+    }
+}