https://kotlinlang.org logo
Channels
100daysofcode
100daysofkotlin
100daysofkotlin-2021
advent-of-code
aem
ai
alexa
algeria
algolialibraries
amsterdam
android
android-architecture
android-databinding
android-studio
androidgithubprojects
androidthings
androidx
androidx-xprocessing
anime
anko
announcements
apollo-kotlin
appintro
arabic
argentina
arkenv
arksemdevteam
armenia
arrow
arrow-contributors
arrow-meta
ass
atlanta
atm17
atrium
austin
australia
austria
awesome-kotlin
ballast
bangladesh
barcelona
bayarea
bazel
beepiz-libraries
belgium
benchmarks
berlin
big-data
books
boston
brazil
brikk
budapest
build
build-tools
bulgaria
bydgoszcz
cambodia
canada
carrat
carrat-dev
carrat-feed
chicago
chile
china
chucker
cincinnati-user-group
cli
clikt
cloudfoundry
cn
cobalt
code-coverage
codeforces
codemash-precompiler
codereview
codingame
codingconventions
coimbatore
collaborations
colombia
colorado
communities
competitive-programming
competitivecoding
compiler
compose
compose-android
compose-desktop
compose-hiring
compose-ios
compose-mp
compose-ui-showcase
compose-wear
compose-web
confetti
connect-audit-events
corda
cork
coroutines
couchbase
coursera
croatia
cryptography
cscenter-course-2016
cucumber-bdd
cyprus
czech
dagger
data2viz
databinding
datascience
dckotlin
debugging
decompose
decouple
denmark
deprecated
detekt
detekt-hint
dev-core
dfw
docs-revamped
dokka
domain-driven-design
doodle
dsl
dublin
dutch
eap
eclipse
ecuador
edinburgh
education
effective-kotlin
effectivekotlin
emacs
embedded-kotlin
estatik
event21-community-content
events
exposed
failgood
fb-internal-demo
feed
firebase
flow
fluid-libraries
forkhandles
forum
fosdem
fp-in-kotlin
framework-elide
freenode
french
fritz2
fuchsia
functional
funktionale
gamedev
ge-kotlin
general-advice
georgia
geospatial
german-lang
getting-started
github-workflows-kt
glance
godot-kotlin
google-io
gradle
graphic
graphkool
graphql
graphql-kotlin
graviton-browser
greece
grpc
gsoc
gui
hackathons
hacktoberfest
hamburg
hamkrest
helios
helsinki
hexagon
hibernate
hikari-cp
hire-me
hiring
hiring-french
hongkong
hoplite
http4k
hungary
hyderabad
image-processing
india
indonesia
inkremental
intellij
intellij-plugins
intellij-tricks
internships
introduce-yourself
io
ios
iran
israel
istanbulcoders
italian
jackson-kotlin
jadx
japanese
jasync-sql
java-to-kotlin-refactoring
javadevelopers
javafx
javalin
javascript
jdbi
jhipster-kotlin
jobsworldwide
jpa
jshdq
juul-libraries
jvm-ir-backend-feedback
jxadapter
k2-early-adopters
kaal
kafka
kakao
kalasim
kapt
karachi
karg
karlsruhe
kash_shell
kaskade
kbuild
kdbc
kgen-doc-tools
kgraphql
kinta
klaxon
klock
kloudformation
kmdc
kmm-español
kmongo
knbt
knote
koalaql
koans
kobalt
kobweb
kodein
kodex
kohesive
koin
koin-dev
komapper
kondor-json
kong
kontent
kontributors
korau
korean
korge
korim
korio
korlibs
korte
kotest
kotest-contributors
kotless
kotlick
kotlin-asia
kotlin-beam
kotlin-by-example
kotlin-csv
kotlin-data-storage
kotlin-foundation
kotlin-fuel
kotlin-in-action
kotlin-inject
kotlin-latam
kotlin-logging
kotlin-multiplatform-contest
kotlin-mumbai
kotlin-native
kotlin-pakistan
kotlin-plugin
kotlin-pune
kotlin-roadmap
kotlin-samples
kotlin-sap
kotlin-serbia
kotlin-spark
kotlin-szeged
kotlin-website
kotlinacademy
kotlinbot
kotlinconf
kotlindl
kotlinforbeginners
kotlingforbeginners
kotlinlondon
kotlinmad
kotlinprogrammers
kotlinsu
kotlintest
kotlintest-devs
kotlintlv
kotlinultimatechallenge
kotlinx-datetime
kotlinx-files
kotlinx-html
kotrix
kotson
kovenant
kprompt
kraph
krawler
kroto-plus
ksp
ktcc
ktfmt
ktlint
ktor
ktp
kubed
kug-leads
kug-torino
kvision
kweb
lambdaworld_cadiz
lanark
language-evolution
language-proposals
latvia
leakcanary
leedskotlinusergroup
lets-have-fun
libgdx
libkgd
library-development
lincheck
linkeddata
lithuania
london
losangeles
lottie
love
lychee
macedonia
machinelearningbawas
madrid
malaysia
mathematics
meetkotlin
memes
meta
metro-detroit
mexico
miami
micronaut
minnesota
minutest
mirror
mockk
moko
moldova
monsterpuzzle
montreal
moonbean
morocco
motionlayout
mpapt
mu
multiplatform
mumbai
munich
mvikotlin
mvrx
myndocs-oauth2-server
naming
navigation-architecture-component
nepal
new-mexico
new-zealand
newname
nigeria
nodejs
norway
npm-publish
nyc
oceania
ohio-kotlin-users
oldenburg
oolong
opensource
orbit-mvi
osgi
otpisani
package-search
pakistan
panamá
pattern-matching
pbandk
pdx
peru
philippines
phoenix
pinoy
pocketgitclient
polish
popkorn
portugal
practical-functional-programming
proguard
prozis-android-backup
pyhsikal
python
python-contributors
quasar
random
re
react
reaktive
realm
realworldkotlin
reductor
reduks
redux
redux-kotlin
refactoring-to-kotlin
reflect
refreshversions
reports
result
rethink
revolver
rhein-main
rocksdb
romania
room
rpi-pico
rsocket
russian
russian_feed
russian-kotlinasfirst
rx
rxjava
san-diego
science
scotland
scrcast
scrimage
script
scripting
seattle
serialization
server
sg-user-group
singapore
skia-wasm-interop-temp
skrape-it
slovak
snake
sofl-user-group
southafrica
spacemacs
spain
spanish
speaking
spek
spin
splitties
spotify-mobius
spring
spring-security
squarelibraries
stackoverflow
stacks
stayhungrystayfoolish
stdlib
stlouis
strife-discord-lib
strikt
students
stuttgart
sudan
swagger-gradle-codegen
swarm
sweden
swing
swiss-user-group
switzerland
talking-kotlin
tallinn
tampa
teamcity
tegal
tempe
tensorflow
terminal
test
testing
testtestest
texas
tgbotapi
thailand
tornadofx
touchlab-tools
training
tricity-kotlin-user-group
trójmiasto
truth
tunisia
turkey
turkiye
twitter-feed
uae
udacityindia
uk
ukrainian
uniflow
unkonf
uruguay
utah
uuid
vancouver
vankotlin
vertx
videos
vienna
vietnam
vim
vkug
vuejs
web-mpp
webassembly
webrtc
wimix_sentry
wwdc
zircon
Powered by
Title
p

Patrick Ramsey

08/23/2021, 11:10 PM
Question: is looking up the dispatcher a job is running on as simple as doing myJob[CoroutineDispatcher]? More generally, what’s the best way to check from a unit test which dispatcher a given job was run on, short of mocking <scope>.launch? Or is that in fact the best way
Rephrased: say I have a piece of synchronous code that calls <scope>.launch(mainDispatcher) { foo }, and I want to test that that code is passing mainDispatcher to launch. Let’s further say that the scope is created inside the object’s constructor (say the object has a lifetime, and should call scope.cancel() when it is destroyed).
Because the scope isn’t injected (it doesn’t make sense (I don’t think!) to inject something that the class theoretically owns, which lives and dies with the instance), there’s no good way to mock <scope>.launch
how then should I validate that the (synchronous) public methods on the object call asynchronous code in the way I expect them to? Or am I thinking about this the wrong way?
a

Alexandre Brown

08/24/2021, 1:57 AM
Hello, I think passing the CoroutineContext/Dispatcher in the constructor is the most robust way to go about this. It allows you to easily pass a synchronous Dispatcher in tests such as TestCoroutineContext. Then in your code you can do launch(myCoroutineContext) or withContext(myCoroutineContext) { } Using Dispatcher.IO in the code instead of using the CoroutineContext from the constructor can result in failing tests. These suggestions are based on the official package kotlinx-coroutines-test.
p

Patrick Ramsey

08/24/2021, 4:38 PM
thanks.
But I’m not sure that quite answers my question. If I have an object with a defined lifecycle, it makes sense for it to have its own CoroutineScope (on which it can call .cancel() when it’s destroyed), right? If that’s true, doesn’t it make sense for the object to be responsible for creating that scope (even if it’s using a passed-in context)? And if that’s true, I’m still not sure I see how best to mock <scope>.launch().
For instance, ViewModel.viewModelScope in androidx.runtime explicitly creates a new CloseableCoroutineScope for the ViewModel it’s called on --- it doesn’t take an injected dispatcher or scope
a

Alexandre Brown

08/24/2021, 4:56 PM
Hello, yes you can have custom scopes, that's quite common in fact. I do not see why you would want to mock it tho. By providing the CoroutineContext via the constructor then you are sure your tests will be synchronous as you can use this CoroutineContext in your custom scope. For the ViewModel and for Main thread operations you must set the Main in the before phase of your tests and clean it up in the after. The doc has the info regarding main thread Coroutine testing.
class MainViewModelTest {
  
    @ExperimentalCoroutinesApi
    private val testDispatcher = TestCoroutineDispatcher()
  
    @Before
    fun setup() {
        // 1
        Dispatchers.setMain(testDispatcher)
    }

    @After
    fun tearDown() {
        // 2
        Dispatchers.resetMain()
        // 3
        testDispatcher.cleanupTestCoroutines()
    }
Here is an example when you want to test business rules class (here JUnit is used but feel free to use whatever you want)
@JvmField
@RegisterExtension
val coroutineTestExtension = CoroutineTestExtension()

@BeforeEach
fun setup() {
   myObjectIWantToTest = MyObject(coroutineTestExtension.testDispatcher)
}
You can even have a base class that uses SupervisorJob in conjonction with the provided coroutine context
class SupervisedCoroutineScopeBehavior(
        private val baseContext: CoroutineContext
) : SupervisedCoroutineScope {

    override val coroutineContext: CoroutineContext
        get() = baseContext + supervisingJob

    private val supervisingJob = SupervisorJob()

    override fun cancelSupervisedJob() {
        supervisingJob.cancel()
    }

    override fun cancelCoroutines() {

        supervisingJob.cancelChildren()
    }
This way child classes provide the base context (in test TestDispatcher, in prod, Dispatchers.Default or IO for instance) yet the class can control its scope lifecycle. The important thing to note here is that we pass the coroutine context via the constructor.
p

Patrick Ramsey

08/24/2021, 5:10 PM
thanks, @Alexandre Brown. Still learning!
🤘 1
So the trick is, I’d like to know whether a certain job is being launched on a certain dispatcher. (Specifically, I want to assert that the work isn’t happening on the main thread). The reason I wanted to mock <scope>.launch() is that seemed like an easy way to accomplish that.
even if I am passing in the coroutine context via the constructor, I’m not sure how best to see what is being added to that context in the call to .launch()
a

Alexandre Brown

08/24/2021, 8:36 PM
I see, I don't think there is an easy way for that besides relying on the fact that coroutines launched on dispatchers not under your control will make the test fail. For instance, if we start collecting on Dispatchers.IO instead of the one from the constructor, then the coroutine will never complete as this is the case with
collect
therefore the test will throw an exception saying that some coroutines are not done when using with
runBlockingTest
. I know this is not quite what you are looking for but to my knowledge (and please if someone knows more please tell us), this is the only way to have this assurance.
p

Patrick Ramsey

08/24/2021, 8:37 PM
… except I suppose by mocking CoroutineScope.launch() hehe
Anyway, thanks 🙂
(sorry, had to shift onto something else for a couple hours)
also, I’ve been avoiding runBlockingTest except when I explicitly need it (so as not to have to wait for delays) since it seems to be buggy https://github.com/Kotlin/kotlinx.coroutines/issues/1204
a

Alexandre Brown

08/24/2021, 8:38 PM
I never tried mocking the coroutine scope but I guess you could try Spying on your class under test ? And verify the number of calls to launch? This seems a bit too hacky to me as it would make your test highly reliant on implementation details but yes maybe that would work.
p

Patrick Ramsey

08/24/2021, 8:39 PM
I kind of prefer mocking external resources to spying on the class under test when I can possibly avoid it
that said, it just hit me that launch() is an extension method
a

Alexandre Brown

08/24/2021, 8:39 PM
Thread.sleep should not be used, runBlockingTest works for delay (the coroutine way)
p

Patrick Ramsey

08/24/2021, 8:39 PM
I’ma see if mockk will let me spy on any<CoroutineScope>().launch()
oh yeah, I definitely am not using Thread.sleep
like I said, I use runBlockingTest when I absolutely have to (to test code with delay()s in it) but it occasionally causes weird failures (see the linked bug)
so most of the time I just use runBlocking {}
I was sort of hoping I could do, e.g.
job[CoroutineDispatcher]
and pull it out of the job’s scope
a

Alexandre Brown

08/24/2021, 8:43 PM
I've been using runBlockingTest for years and I agree that it wasn't quite stable at first but right now I believe we should all use it as this is the recommended approach by JetBrains. Using runBlocking under test is not advised. The link you sent me seems to be an issue when you do not use the TestDispatcher and pass it to the constructor of the system under test in which case the exception "This job has not completed yet" is expected and actually a good thing. This is precisely what I was referring to earlier, you can test that your class is using your coroutine context because if it is not it will fail.
p

Patrick Ramsey

08/24/2021, 8:43 PM
but how that works is a little magic to me and I’m not entirely clear on what should work and what shouldn’t
@Alexandre Brown that bug is still listed as open, and we’re still experiencing it
that said, I think we’re now a few versions behind. We just hadn’t tried bumping in a while because there wasn’t anything we needed and the bug was still open
err, wait. I responded before I’d fully grasped what you were saying
So… runBlockingTest { } should replace Dispatches.IO, Dispatchers.Default, and call Dispatchers.setMain(), no?
a

Alexandre Brown

08/24/2021, 8:46 PM
No
p

Patrick Ramsey

08/24/2021, 8:46 PM
let me go look at the implementation again, it’s been months since I’ve thought about this
yes, apologies, I don’t know what I was thinking
a

Alexandre Brown

08/24/2021, 8:50 PM
I will let you check it out but the setup should look like this : 1. Create the test dispatcher 2. Use the test dispatcher to set the main 3. Pass the test dispatcher as coroutine context to your class under test 4. In your class under test, make sure to launch or async using the coroutine context passed from the constructor 5. Use runBlockingTest if your test code needs to suspend (eg: the call to your class suspends), otherwise no need to use runBlocking or runBlockingTest If you use JUnit 5 here is an extension that you can register
class CoroutineTestExtension(
        val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
) : BeforeEachCallback, AfterEachCallback, TestCoroutineScope by TestCoroutineScope(testDispatcher) {

    override fun beforeEach(context: ExtensionContext?) {
        Dispatchers.setMain(testDispatcher)
    }

    override fun afterEach(context: ExtensionContext?) {
        cleanupTestCoroutines()
        Dispatchers.resetMain()
    }
}
p

Patrick Ramsey

08/24/2021, 8:58 PM
that still doesn’t easily let me validate that it didn’t run on the main dispatcher — unless I create two separate test dispatchers (default and main), pass them both in, then somehow interrogate them to see what jobs were scheduled on them
which seems like potentially the right way to go about things, I just need to go figure out how to do that.
a

Alexandre Brown

08/24/2021, 9:05 PM
Indeed I was just replying to the discussion we had about runBlockingTest not working and how to set it up. For your initial question, I think you are right that maybe mocking the scope (which in case the system under test implements CoroutineScope would be the class under test) then verifying that the expected coroutine scope was used might do it but I did not test this. I will let you try it out.
p

Patrick Ramsey

08/24/2021, 9:07 PM
thanks. Sorry for dragging this out so far
a

Alexandre Brown

08/24/2021, 9:08 PM
The pleasure is mind 🙂
p

Patrick Ramsey

08/24/2021, 9:08 PM
I think you’ve pointed out a fundamental misunderstanding I probably ended up with pretty early on in taking on this codebase then didn’t notice as I moved onto other things
thanks for fixing my brain!
one thing I forsee being a struggle is this code has a lot of direct references to Dispatchers.*. There’s been a sometimes-on, sometimes-off effort to move us cloesr to dependency injection, but we’re not there yet
j

Joe

08/25/2021, 2:10 AM
It's almost certainly not a stable way to do it, but we've asserted on the
toString()
of
coroutineContext[CoroutineDispathcer]
to make sure things run on the right dispatcher. Assume this will break eventually but has worked for now. example test code: https://github.com/trib3/leakycauldron/blob/6229b4fa720c29cc4167b832c3dc2014a85476[…]in/com/trib3/server/coroutine/CoroutineInvocationHandlerTest.kt
p

Patrick Ramsey

08/25/2021, 2:11 AM
righto.
Thanks. Right now I’m trying to see if it’s possible to instead mock the code that gets called from within launch { } to check which dispatcher is in the context from there
since that’s ultimately what matters: “this code needs to end up running on the main thread because android expects it”
“This code needs to not be on the main thread because it’ll block”