Skip to content

Unit-Testing with Coroutines results in "You may not call the store from a thread other than the thread on which it was created" #38

@jennymolske

Description

@jennymolske

Hi,

we currently use your library in combination with Kotlin Coroutines and Flow. One of our middleware methods looks like this:

private fun agbLoad(store: Store<AppState>, action: AgbLoad, next: Dispatcher) {
        CoroutineScope(Main).launch {
            flow { emit(apiService.client().user().api().termsOfUse()) }
                    .flowOn(IO)
                    // flowOn only works upstream.
                    // Catch & Collect are executed on the main thread
                    .catch { e ->
                        e.printStackTrace()
                        store.dispatch(AgbLoadFailure(e))
                    }
                    .collect { agb -> store.dispatch(AgbLoadSuccess(LegalState.Agb(agb.text, agb.activeFrom.ddMMYYYY()))) }
        }

        next(action)
    }

We wanted to test this (and the other functions) and wrote the following JUnit Test:

    fun setUp() {
        Dispatchers.setMain(mainThreadSurrogate)
        recorderMiddleware = RecorderMiddleware()
        legalMiddleware = LegalMiddleware(apiService(), assetFeatureMock, userServiceMock)
    }
    @Test
    fun agbLoad_dispatchesAgbLoadSuccess() {
        webServer().setDispatcher(object : Dispatcher() {
            override fun dispatch(request: RecordedRequest?): MockResponse {
                return MockResponse().setResponseCode(HttpURLConnection.HTTP_OK).setBody(termsOfUseResponseJson())
            }
        })

        runBlocking {
            CoroutineScope(Main).launch {
                val store = createStore(appReducer, AppState(), applyMiddleware(
                        recorderMiddleware.all, LegalMiddleware(apiService(), assetFeatureMock, userServiceMock).all
                ))

                store.dispatch(AgbLoad())
            }
        }

        Barrier.awaitUntil { recorderMiddleware.getRecordings().size == 2 }
        assertThat(recorderMiddleware.getRecordings().last(), instanceOf(AgbLoadSuccess::class.java))
    }

    @After
    fun tearDown() {
        Dispatchers.resetMain() // reset main dispatcher to the original Main dispatcher
        mainThreadSurrogate.close()
    }

Unfortunately the test throws an error:
Exception in thread "UI @coroutine#3" java.lang.IllegalStateException: You may not call the store from a thread other than the thread on which it was created. This includes: getState(), dispatch(), subscribe(), and replaceReducer() This store was created on: 'UI @coroutine#2' and current thread is 'UI @coroutine#3' at org.reduxkotlin.CreateStoreKt$createStore$3.invoke(CreateStore.kt:50) at org.reduxkotlin.CreateStoreKt$createStore$7.invoke(CreateStore.kt:174)

I already tried lots of coroutine combinations to get rid of this issue, but no success. Do you have an idea how we can fix this?

(additional information): The exception doesn't appear in production, only in the test scenario.

Thanks for your help :)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions