question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Feature request for mocks to have thenThrowUnchecked, as the updated thenThrow is troublesome when used with RXJava

See original GitHub issue

check that

  • The mockito message in the stacktrace have useful information, but it didn’t help
  • The problematic code (if that’s possible) is copied here; Note that some configuration are impossible to mock via Mockito
  • Provide versions (mockito / jdk / os / any other relevant information) project uses gradle ‘4.4.1’, mockito ‘2.8.47’, RXJava ‘2.1.8’, RXAndroid ‘2.0.1’
  • Provide a Short, Self Contained, Correct (Compilable), Example of the issue
  • Read the contributing guide

Note: This is related to a fix listed in issue #1155 and fixed in pull request #1162

I wanted to point out how that bug-fix affects development with RXJava. For anyone who is using RXJava - that fix forced a lot of changes in the code, that are not desirable. Forcing the function definition to state that it throws leads to code smells and try catch blocks that don’t really do anything, as exception handling is implemented inside RXJava.

For example: I have a class that returns an Observable, for let’s say updating my users password, however the function may throw if the server returns an error code:

UpdatePassword.java

final public class UpdatePassword extends Interactor<Boolean, UpdatePassword.Params> {
    private AccountService accountService;

    @Inject
    public UpdatePassword(SchedulerProvider schedulerProvider, AccountService accountService) {
        super(schedulerProvider);
        this.accountService = accountService;
    }

    @Override
    public Observable<Boolean> observable(Params params) {
        return accountService.updatePassword(params);
    }
}

AccountService.java

public class AccountService extends BaseService {
\* (...) *\
        public Observable<Boolean> updatePassword(UpdatePassword.Params params) {
        return accountApi.updatePassword(new ApiRequestBody<>(params))
            .map(response -> {
                if (response.errorCode > 0 && response.messageKey != null) {
                    throw new ApiException(response.message, response.errorCode, response.messageKey);
                }
                return response.status == 200;
            })
    }
}

This code would be called in the following way:

UpdatePassword updatePassword;
\* (...) *\

@OnClick(R.id.update_password_button) void onUpdateButtonClick() {

updatePassword.observable(params).subscribe(
    passwordUpdated -> {
        if (passwordUpdated) { 
            /* (...) */
        }
    }, throwable -> { // Here is where code handles Observables Exception 
        if (throwable instanceof ApiException) {
            /* (...) */
        }
        else {
            view.onError(throwable.getMessage());
        }
    }, this::hideLoadingScreen);
}

And this was my test scenario (all I’m doing is mocking the AccountService and make sure that the UpdatePassword indeed calls the method from injected AccountService):

public class UpdatePasswordTest {

    private TestSchedulerProvider testSchedulerProvider = new TestSchedulerProvider();
    private AccountService mockAccountService;
    private AppConfig mockAppConfig;
    private UpdatePassword updatePassword;
    private Boolean isUpdated = true;
    private UpdatePassword.Params testParams;

    @Before
    public void setup() {
        mockAccountService = mock(AccountService.class);
        mockAppConfig = mock(AppConfig.class);

        testParams = new UpdatePassword.Params(mockAppConfig, "oldPassword","newPassword", "confirmPassword");

        when(mockAccountService.updatePassword(any()))
                .thenReturn(Observable.just(isUpdated))
                .thenThrow(ApiException.class);
        updatePassword = new UpdatePassword(testSchedulerProvider, mockAccountService);
    }

    @Test
    public void test_passwordUpdateObservable_returnsTrue() {
        Observable<Boolean> passwordObservable = updatePassword.observable(testParams);
        TestObserver<Boolean> testObserver = passwordObservable.test();

        testObserver.assertNoErrors();
        testObserver.assertValue(isUpdated);
        testObserver.assertComplete();
    }

    @Test (expected = ApiException.class)
    public void test_passwordUpdateObservable_throws() {
        TestObserver<Boolean> testObserver = new TestObserver<>();

        updatePassword.observable(testParams).subscribeWith(testObserver);
        updatePassword.observable(testParams).subscribeWith(testObserver);
    }
}

The above scenario works great with mockito version 2.8.47, but fails, when using any higher version.

So if I update mockito to e.g. 2.11.0, after running tests I get the error below

org.mockito.exceptions.base.MockitoException: 
Checked exception is invalid for this method!

which forces me to update the definition of updatePassword function in AccountService class, which forces to update the definition of observable function for every single class that extends the Interactor, when in reality there’s only a few classes that do throw. Moreover, now I have to wrap the call to get the observable inside of the @OnClick into try/catch block, but I’m not planning on catching anything, since the RXJava’s throwable piece is taking care of that. Also, those changes trickle down to having the following test case scenario:

public class UpdatePasswordTest {

    private TestSchedulerProvider testSchedulerProvider = new TestSchedulerProvider();
    private AccountService mockAccountService;
    private AppConfig mockAppConfig;
    private UpdatePassword updatePassword;
    private Boolean isUpdated = true;
    private UpdatePassword.Params testParams;

    @Before
    public void setup() {
        mockAccountService = mock(AccountService.class);
        mockAppConfig = mock(AppConfig.class);

        testParams = new UpdatePassword.Params(mockAppConfig, "oldPassword","newPassword", "confirmPassword");

        try {
            when(mockAccountService.updatePassword(any()))
                    .thenReturn(Observable.just(isUpdated))
                    .thenThrow(ApiException.class);
            updatePassword = new UpdatePassword(testSchedulerProvider, mockAccountService);
        } catch (Exception e) { }
    }

    @Test
    public void test_passwordUpdateObservable_returnsTrue() {
        try {
            Observable<Boolean> passwordObservable = updatePassword.observable(testParams);
            TestObserver<Boolean> testObserver = passwordObservable.test();

            testObserver.assertNoErrors();
            testObserver.assertValue(isUpdated);
            testObserver.assertComplete();
        }
        catch(Exception e) { }
    }

    @Test (expected = ApiException.class)
    public void test_passwordUpdateObservable_throws() {
        try {
            TestObserver<Boolean> testObserver = new TestObserver<>();

            updatePassword.observable(testParams).subscribeWith(testObserver);
            updatePassword.observable(testParams).subscribeWith(testObserver);
        }
        catch(Exception e) { }
    }
}

The compiler basically forced me to add all those try/catch blocks, otherwise my code wouldn’t compile, since observable was marked as throwing.

So basically whoever wants to use the unchecked exceptions as a feature, should use mockito version 2.8.47, because any higher than that, tests would start failing.

And that is why I wanted to ask whether it would be possible to add thenThrowUnchecked function to mocks, that would preserve the old (and highly desired by project I’m working on) behavior.

I would love to be able to define that my mockAccountService throws an unchecked exception. e.g. like this:

when(mockAccountService.updatePassword(any()))
                    .thenReturn(Observable.just(isUpdated))
                    .thenThrowUnchecked(ApiException.class);

Many thanks if you’ve read until the end 😉

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Comments:8 (2 by maintainers)

github_iconTop GitHub Comments

8reactions
mkmaiercommented, May 22, 2018

For anyone else looking for a generic solution, this does the trick:

when(...).thenAnswer(invocation -> { throw new IOException(); });
1reaction
tmurakamicommented, Jan 18, 2018

Have you tried Observable#error(Throwable)?

when(mockAccountService.updatePassword(any()))
                    .thenReturn(Observable.just(isUpdated))
                    .thenReturn(Observable.error(new ApiException(...)));

https://stackoverflow.com/questions/44690008/how-to-handle-mocked-rxjava2-observable-throwing-exception-in-unit-test

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to handle mocked RxJava2 observable throwing ...
thenReturn(Observable.error()) ? I think the way you have it the exception is thrown outside of the Rx stream. Hence the onError doesn't happen ......
Read more >
Error handling in RxJava - Dan Lew Codes
It means that there was a problem in processing the current item such that no future processing of any items can occur.
Read more >
Mock Server Errors w/ Retrofit and RxJava - malachid
When you call the mycall() function, it accesses MYPATH on the server, via GET , converts the results into MYPOJO and wraps it...
Read more >
350% performance improvement in fetching data from the ...
Fetching data from different API calls, and displaying the data aggregated in a RecyclerView, using RxJava; Cache recently fetched data in a local...
Read more >
OKHttp Error Interceptors, RxJava 2, Repository Pattern, Retrofit
We'll use Retrofit, OKHttp, and a bit of RxJava magic to make it all work nicely ... If you're new to Rx, the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found