Robolectric Cannot invoke setValue on a background thread

2.1k Views Asked by At

I am testing an AsyncTask that onPostExecute calls setValue of a LiveData instance. Since I am invoking setValue from onPostExecute no issues were expected regarding the invocation being done by the UI thread.

Yet running this in a Robolectric unit test I got: java.lang.IllegalStateException: Cannot invoke setValue on a background thread

To make this unit test wait for background and foreground tasks completion I take advantage of awaitility tool in the following way:

var cf = new CompletableFuture<T>();
livedata.observe(ctrl.get(), it -> cf.complete(it));
// ... perform the AsyncTask that will update livedata in onPostExecute
await().until(() -> {
    flushBackgroundThreadScheduler()
    flushForegroundThreadScheduler()
    cf.isDone
});

This is throwing an IllegalStateException: Cannot invoke setValue on a background thread on flushForegroundThreadScheduler() call!!!!

Why I am getting this exception? And how can I have the onPostExecute being performed like in the UI thread?

UPDATE

Logging threads it seems that both flushBackgroundThreadScheduler() and flushForegroundThreadScheduler() are executed synchronously inline. I can observe:

LiveData created on thread 763324286
Background thread 1519527121
LiveData updated on thread 1519527121

Since the lambda passed to await.until runs on another thread, then both flushBackgroundThreadScheduler() and flushForegroundThreadScheduler() are performed on that thread 1519527121.

Thus, I can solve my problem with the following workaround running in the test thread corresponding to UI Thread. Yet, I need that Thread.sleep() to succeed and I don't like it.

Thread.sleep(1000)
flushBackgroundThreadScheduler()
flushForegroundThreadScheduler()
cf.isDone
1

There are 1 best solutions below

0
On

We must have the following considerations into account regarding:

  1. Robolectric: flushForegroundThreadScheduler() is executed synchronously inline.
  2. Awaitility: await.until(<Callable>) evaluates the until condition in a background thread.

From these two statements we observe that invoking flushForegroundThreadScheduler() from the Callable passed to await.until(<Callable>) results in the invocation of scheduled foreground tasks in a background thread, which answers the first OP question: Why I am getting this exception?

Answering the second OP question:

how can I have the onPostExecute being performed like in the UI thread?

Since Robolectric shares a single thread for both UI operations and Test code, then we must use pollInSameThread to instruct that the until condition should be evaluated on the same thread as the test case that starts Awaitility. The OP sample code should be fixed to:

await().pollInSameThread().until(() -> {
    flushBackgroundThreadScheduler()
    flushForegroundThreadScheduler()
    cf.isDone
});