2

I have a class that looks something like

class Foo {
  private val scope = Job() + Dispatchers.IO
  val emissions = PublishSubject.create<Bar>()

  fun doSomething() {
    scope.launch {
      // Do a whole bunch of work...
      withContext(Dispatchers.Main) emissions.onNext(bar)
    }
  }
}

And I'm trying to come up with a way to unit test it. I've tried making the scope injectable and writing something like

@Test fun testFoo() = runBlockingTest {
  Dispatchers.setMain(TestCoroutineDispatcher())
  val foo = Foo(this)
  foo.doSomething()
  foo.emissions.assertStuff()
}

but that doesn't seem to work. The assertion happens before the coroutine inside doSomething() has finished.

I've also tried making this dispatchers injectable, providing Dispatchers.Unconfined, but that didn't help either. Is there something wrong with this approach?

2
  • Does it have to be a PublishSubject? Commented Oct 7, 2019 at 3:34
  • Needs to be a Subject, Relay,` Channel` or something similar Commented Oct 7, 2019 at 3:48

2 Answers 2

0

If you are able to expose the job for public API, you can try

class Foo {
  private val scope: CoroutineScope = CoroutineScope(Job() + Dispatchers.IO)
  val emissions = PublishSubject.create<Bar>()

  fun doSomething() = scope.launch {
    // Do a whole bunch of work...
    withContext(Dispatchers.Main) { emissions.onNext(bar) }
  }
}

class Test {
  private val testFoo = Foo()
  private val testObserver: TestObserver<Bar> = TestObserver.create()

  @BeforeEach
  fun setUp() {
    testFoo.emissions.subscribe(testObserver)
  }

  @Test fun testFoo() {
    runBlockingTest {
      Dispatchers.setMain(TestCoroutineDispatcher())
      testFoo.doSomething().join()
      testObserver.assertValue(bar)
    }
  }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Exposing a Job means that external callers can cancel the coroutine started by launch, which I don't want.
0

Testing asynchronous code can be done well with this library: Awaitility (or its kotlin extension)

You would write something like:

@Test fun testFoo() {

  val foo = Foo()

  foo.doSomething()

  await().atMost(5, MILLIS).until(/* your check like - foo.emissions.onNext(bar) */);
}

1 Comment

Hmm. That would work, but I'm wondering if there's a more elegant solution with blocking dispatchers.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.