How to unit test coroutine when it contains coroutine delay?











up vote
1
down vote

favorite












When I add a coroutine delay() in my view model, the remaining part of the code will not be executed.



This is my demo code:



class SimpleViewModel : ViewModel(), CoroutineScope {

override val coroutineContext: CoroutineContext
get() = Dispatchers.Unconfined

var data = 0

fun doSomething() {
launch {
delay(1000)
data = 1
}
}
}

class ScopedViewModelTest {

@Test
fun coroutineDelay() {
// Arrange
val viewModel = SimpleViewModel()

// ActTes
viewModel.doSomething()

// Assert
Assert.assertEquals(1, viewModel.data)
}
}


I got the assertion result:



java.lang.AssertionError: 
Expected :1
Actual :0


Any idea how to fix this?










share|improve this question




























    up vote
    1
    down vote

    favorite












    When I add a coroutine delay() in my view model, the remaining part of the code will not be executed.



    This is my demo code:



    class SimpleViewModel : ViewModel(), CoroutineScope {

    override val coroutineContext: CoroutineContext
    get() = Dispatchers.Unconfined

    var data = 0

    fun doSomething() {
    launch {
    delay(1000)
    data = 1
    }
    }
    }

    class ScopedViewModelTest {

    @Test
    fun coroutineDelay() {
    // Arrange
    val viewModel = SimpleViewModel()

    // ActTes
    viewModel.doSomething()

    // Assert
    Assert.assertEquals(1, viewModel.data)
    }
    }


    I got the assertion result:



    java.lang.AssertionError: 
    Expected :1
    Actual :0


    Any idea how to fix this?










    share|improve this question


























      up vote
      1
      down vote

      favorite









      up vote
      1
      down vote

      favorite











      When I add a coroutine delay() in my view model, the remaining part of the code will not be executed.



      This is my demo code:



      class SimpleViewModel : ViewModel(), CoroutineScope {

      override val coroutineContext: CoroutineContext
      get() = Dispatchers.Unconfined

      var data = 0

      fun doSomething() {
      launch {
      delay(1000)
      data = 1
      }
      }
      }

      class ScopedViewModelTest {

      @Test
      fun coroutineDelay() {
      // Arrange
      val viewModel = SimpleViewModel()

      // ActTes
      viewModel.doSomething()

      // Assert
      Assert.assertEquals(1, viewModel.data)
      }
      }


      I got the assertion result:



      java.lang.AssertionError: 
      Expected :1
      Actual :0


      Any idea how to fix this?










      share|improve this question















      When I add a coroutine delay() in my view model, the remaining part of the code will not be executed.



      This is my demo code:



      class SimpleViewModel : ViewModel(), CoroutineScope {

      override val coroutineContext: CoroutineContext
      get() = Dispatchers.Unconfined

      var data = 0

      fun doSomething() {
      launch {
      delay(1000)
      data = 1
      }
      }
      }

      class ScopedViewModelTest {

      @Test
      fun coroutineDelay() {
      // Arrange
      val viewModel = SimpleViewModel()

      // ActTes
      viewModel.doSomething()

      // Assert
      Assert.assertEquals(1, viewModel.data)
      }
      }


      I got the assertion result:



      java.lang.AssertionError: 
      Expected :1
      Actual :0


      Any idea how to fix this?







      android unit-testing kotlin coroutine kotlinx.coroutines






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Nov 13 at 0:02

























      asked Nov 12 at 23:34









      Cody

      2,43322829




      2,43322829
























          2 Answers
          2






          active

          oldest

          votes

















          up vote
          1
          down vote



          accepted










          You start a coroutine which suspends for 1 second before setting data to 1. Your test just invokes doSomething but does not wait until data is actually being set. If you add another, longer delay, to the test it will, work:



          @Test     
          fun coroutineDelay() = runBlocking {
          ...
          viewModel.doSomething()
          delay(1100)
          ...
          }


          You can also make the coroutine return a Deferred which you can wait on:



          fun doSomething(): Deferred<Unit> {
          return async {
          delay(1000)
          data = 1
          }
          }


          With await there's no need to delay your code anymore:



          val model = SimpleViewModel()
          model.doSomething().await()





          share|improve this answer























          • Is there a way to not including the specific delay number in the test?
            – Cody
            Nov 13 at 0:20








          • 1




            with async for example
            – s1m0nw1
            Nov 13 at 0:36


















          up vote
          2
          down vote













          The first issue in your code is that SimpleViewModel.coroutineContext has no Job associated with it. The whole point of making your view model a CoroutineScope is the ability to centralize the cancelling of all coroutines it starts. So add the job as follows (note the absence of a custom getter):



          class SimpleViewModel : ViewModel(), CoroutineScope {

          override val coroutineContext = Job() + Dispatchers.Unconfined

          var data = 0

          fun doSomething() {
          launch {
          delay(1000)
          data = 1
          }
          }
          }


          Now your test code can ensure it proceeds to the assertions only after all the jobs your view model launched are done:



          class ScopedViewModelTest {

          @Test
          fun coroutineDelay() {
          // Arrange
          val viewModel = SimpleViewModel()

          // ActTes
          viewModel.doSomething()

          // Assert
          runBlocking {
          viewModel.coroutineContext[Job]!!.children.forEach { it.join() }
          }
          Assert.assertEquals(1, viewModel.data)
          }
          }





          share|improve this answer





















            Your Answer






            StackExchange.ifUsing("editor", function () {
            StackExchange.using("externalEditor", function () {
            StackExchange.using("snippets", function () {
            StackExchange.snippets.init();
            });
            });
            }, "code-snippets");

            StackExchange.ready(function() {
            var channelOptions = {
            tags: "".split(" "),
            id: "1"
            };
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function() {
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled) {
            StackExchange.using("snippets", function() {
            createEditor();
            });
            }
            else {
            createEditor();
            }
            });

            function createEditor() {
            StackExchange.prepareEditor({
            heartbeatType: 'answer',
            convertImagesToLinks: true,
            noModals: true,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: 10,
            bindNavPrevention: true,
            postfix: "",
            imageUploader: {
            brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
            contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
            allowUrls: true
            },
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            });


            }
            });














             

            draft saved


            draft discarded


















            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53271646%2fhow-to-unit-test-coroutine-when-it-contains-coroutine-delay%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown

























            2 Answers
            2






            active

            oldest

            votes








            2 Answers
            2






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes








            up vote
            1
            down vote



            accepted










            You start a coroutine which suspends for 1 second before setting data to 1. Your test just invokes doSomething but does not wait until data is actually being set. If you add another, longer delay, to the test it will, work:



            @Test     
            fun coroutineDelay() = runBlocking {
            ...
            viewModel.doSomething()
            delay(1100)
            ...
            }


            You can also make the coroutine return a Deferred which you can wait on:



            fun doSomething(): Deferred<Unit> {
            return async {
            delay(1000)
            data = 1
            }
            }


            With await there's no need to delay your code anymore:



            val model = SimpleViewModel()
            model.doSomething().await()





            share|improve this answer























            • Is there a way to not including the specific delay number in the test?
              – Cody
              Nov 13 at 0:20








            • 1




              with async for example
              – s1m0nw1
              Nov 13 at 0:36















            up vote
            1
            down vote



            accepted










            You start a coroutine which suspends for 1 second before setting data to 1. Your test just invokes doSomething but does not wait until data is actually being set. If you add another, longer delay, to the test it will, work:



            @Test     
            fun coroutineDelay() = runBlocking {
            ...
            viewModel.doSomething()
            delay(1100)
            ...
            }


            You can also make the coroutine return a Deferred which you can wait on:



            fun doSomething(): Deferred<Unit> {
            return async {
            delay(1000)
            data = 1
            }
            }


            With await there's no need to delay your code anymore:



            val model = SimpleViewModel()
            model.doSomething().await()





            share|improve this answer























            • Is there a way to not including the specific delay number in the test?
              – Cody
              Nov 13 at 0:20








            • 1




              with async for example
              – s1m0nw1
              Nov 13 at 0:36













            up vote
            1
            down vote



            accepted







            up vote
            1
            down vote



            accepted






            You start a coroutine which suspends for 1 second before setting data to 1. Your test just invokes doSomething but does not wait until data is actually being set. If you add another, longer delay, to the test it will, work:



            @Test     
            fun coroutineDelay() = runBlocking {
            ...
            viewModel.doSomething()
            delay(1100)
            ...
            }


            You can also make the coroutine return a Deferred which you can wait on:



            fun doSomething(): Deferred<Unit> {
            return async {
            delay(1000)
            data = 1
            }
            }


            With await there's no need to delay your code anymore:



            val model = SimpleViewModel()
            model.doSomething().await()





            share|improve this answer














            You start a coroutine which suspends for 1 second before setting data to 1. Your test just invokes doSomething but does not wait until data is actually being set. If you add another, longer delay, to the test it will, work:



            @Test     
            fun coroutineDelay() = runBlocking {
            ...
            viewModel.doSomething()
            delay(1100)
            ...
            }


            You can also make the coroutine return a Deferred which you can wait on:



            fun doSomething(): Deferred<Unit> {
            return async {
            delay(1000)
            data = 1
            }
            }


            With await there's no need to delay your code anymore:



            val model = SimpleViewModel()
            model.doSomething().await()






            share|improve this answer














            share|improve this answer



            share|improve this answer








            edited Nov 13 at 0:36

























            answered Nov 13 at 0:08









            s1m0nw1

            23.9k53696




            23.9k53696












            • Is there a way to not including the specific delay number in the test?
              – Cody
              Nov 13 at 0:20








            • 1




              with async for example
              – s1m0nw1
              Nov 13 at 0:36


















            • Is there a way to not including the specific delay number in the test?
              – Cody
              Nov 13 at 0:20








            • 1




              with async for example
              – s1m0nw1
              Nov 13 at 0:36
















            Is there a way to not including the specific delay number in the test?
            – Cody
            Nov 13 at 0:20






            Is there a way to not including the specific delay number in the test?
            – Cody
            Nov 13 at 0:20






            1




            1




            with async for example
            – s1m0nw1
            Nov 13 at 0:36




            with async for example
            – s1m0nw1
            Nov 13 at 0:36












            up vote
            2
            down vote













            The first issue in your code is that SimpleViewModel.coroutineContext has no Job associated with it. The whole point of making your view model a CoroutineScope is the ability to centralize the cancelling of all coroutines it starts. So add the job as follows (note the absence of a custom getter):



            class SimpleViewModel : ViewModel(), CoroutineScope {

            override val coroutineContext = Job() + Dispatchers.Unconfined

            var data = 0

            fun doSomething() {
            launch {
            delay(1000)
            data = 1
            }
            }
            }


            Now your test code can ensure it proceeds to the assertions only after all the jobs your view model launched are done:



            class ScopedViewModelTest {

            @Test
            fun coroutineDelay() {
            // Arrange
            val viewModel = SimpleViewModel()

            // ActTes
            viewModel.doSomething()

            // Assert
            runBlocking {
            viewModel.coroutineContext[Job]!!.children.forEach { it.join() }
            }
            Assert.assertEquals(1, viewModel.data)
            }
            }





            share|improve this answer

























              up vote
              2
              down vote













              The first issue in your code is that SimpleViewModel.coroutineContext has no Job associated with it. The whole point of making your view model a CoroutineScope is the ability to centralize the cancelling of all coroutines it starts. So add the job as follows (note the absence of a custom getter):



              class SimpleViewModel : ViewModel(), CoroutineScope {

              override val coroutineContext = Job() + Dispatchers.Unconfined

              var data = 0

              fun doSomething() {
              launch {
              delay(1000)
              data = 1
              }
              }
              }


              Now your test code can ensure it proceeds to the assertions only after all the jobs your view model launched are done:



              class ScopedViewModelTest {

              @Test
              fun coroutineDelay() {
              // Arrange
              val viewModel = SimpleViewModel()

              // ActTes
              viewModel.doSomething()

              // Assert
              runBlocking {
              viewModel.coroutineContext[Job]!!.children.forEach { it.join() }
              }
              Assert.assertEquals(1, viewModel.data)
              }
              }





              share|improve this answer























                up vote
                2
                down vote










                up vote
                2
                down vote









                The first issue in your code is that SimpleViewModel.coroutineContext has no Job associated with it. The whole point of making your view model a CoroutineScope is the ability to centralize the cancelling of all coroutines it starts. So add the job as follows (note the absence of a custom getter):



                class SimpleViewModel : ViewModel(), CoroutineScope {

                override val coroutineContext = Job() + Dispatchers.Unconfined

                var data = 0

                fun doSomething() {
                launch {
                delay(1000)
                data = 1
                }
                }
                }


                Now your test code can ensure it proceeds to the assertions only after all the jobs your view model launched are done:



                class ScopedViewModelTest {

                @Test
                fun coroutineDelay() {
                // Arrange
                val viewModel = SimpleViewModel()

                // ActTes
                viewModel.doSomething()

                // Assert
                runBlocking {
                viewModel.coroutineContext[Job]!!.children.forEach { it.join() }
                }
                Assert.assertEquals(1, viewModel.data)
                }
                }





                share|improve this answer












                The first issue in your code is that SimpleViewModel.coroutineContext has no Job associated with it. The whole point of making your view model a CoroutineScope is the ability to centralize the cancelling of all coroutines it starts. So add the job as follows (note the absence of a custom getter):



                class SimpleViewModel : ViewModel(), CoroutineScope {

                override val coroutineContext = Job() + Dispatchers.Unconfined

                var data = 0

                fun doSomething() {
                launch {
                delay(1000)
                data = 1
                }
                }
                }


                Now your test code can ensure it proceeds to the assertions only after all the jobs your view model launched are done:



                class ScopedViewModelTest {

                @Test
                fun coroutineDelay() {
                // Arrange
                val viewModel = SimpleViewModel()

                // ActTes
                viewModel.doSomething()

                // Assert
                runBlocking {
                viewModel.coroutineContext[Job]!!.children.forEach { it.join() }
                }
                Assert.assertEquals(1, viewModel.data)
                }
                }






                share|improve this answer












                share|improve this answer



                share|improve this answer










                answered Nov 16 at 9:48









                Marko Topolnik

                143k18193320




                143k18193320






























                     

                    draft saved


                    draft discarded



















































                     


                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function () {
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53271646%2fhow-to-unit-test-coroutine-when-it-contains-coroutine-delay%23new-answer', 'question_page');
                    }
                    );

                    Post as a guest















                    Required, but never shown





















































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown

































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown







                    Popular posts from this blog

                    Biblatex bibliography style without URLs when DOI exists (in Overleaf with Zotero bibliography)

                    ComboBox Display Member on multiple fields

                    Is it possible to collect Nectar points via Trainline?