Feature request for mocks to have thenThrowUnchecked, as the updated thenThrow is troublesome when used with RXJava
See original GitHub issuecheck 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:
- Created 6 years ago
- Comments:8 (2 by maintainers)
Top GitHub Comments
For anyone else looking for a generic solution, this does the trick:
Have you tried
Observable#error(Throwable)
?https://stackoverflow.com/questions/44690008/how-to-handle-mocked-rxjava2-observable-throwing-exception-in-unit-test