This question has been asked before but the existing answers don't quite apply to my situation.
I'd like to test the submitCode()
method:
public class VerificationCodeViewModel{
//Input
public final ObservableField<String> verificationCode = new ObservableField<>();
//Output
public final ObservableField<String> requestError = new ObservableField<>();
public final ObservableBoolean loading = new ObservableBoolean();
public final ObservableField<LoginCredentials> loginCredentials = new ObservableField<>();
@NonNull private final Context context;
@NonNull private final UnverifiedUser unverifiedUser;
@NonNull private final CampaignRepository campaignRepository;
@NonNull private final AccountRepository accountRepository;
@NonNull private final VerificationCodeNavigator navigator;
public VerificationCodeViewModel(@NonNull Context context,
@NonNull UnverifiedUser unverifiedUser,
@NonNull CampaignRepository campaignRepository,
@NonNull AccountRepository accountRepository,
@NonNull VerificationCodeNavigator navigator) {
this.context = context;
this.unverifiedUser = unverifiedUser;
this.campaignRepository = campaignRepository;
this.accountRepository = accountRepository;
this.navigator = navigator;
}
public void submitCode() {
loading.set(true);
String sourceCampaign = null;
if (campaignRepository.getCampaign() != null) {
sourceCampaign = campaignRepository.getCampaign().getSource();
}
this.accountRepository.verifyMobileNumber(
this.unverifiedUser,
this.verificationCode.get(),
sourceCampaign,
new AccountDataSource.VerifyMobileNumberCallback() {
@Override
public void onVerificationSuccess(UnverifiedUser.Entity entity) {
loading.set(false);
loginCredentials.set(createLoginCredentials());
navigator.finishActivity(true);
}
@Override
public void onVerificationFailure(@Nullable String message) {
loading.set(false);
requestError.set(message);
}
}
);
}
}
I have the following test case:
public class VerificationCodeViewModelTests {
private VerificationCodeViewModel viewModel;
@Mock private Context context;
@Mock private UnverifiedUser unverifiedUser;
@Mock private CampaignRepository campaignRepository;
@Mock private AccountRepository accountRepository;
@Mock private VerificationCodeNavigator navigator;
@Mock private ArgumentCaptor<AccountDataSource.VerifyMobileNumberCallback> verifyMobileNumberCallbackCaptor;
@Before
public void setupVerificationCodeViewModel(){
MockitoAnnotations.initMocks(this);
viewModel = new VerificationCodeViewModel(
context,
unverifiedUser,
campaignRepository,
accountRepository,
mock(VerifyMobileNumberActivity.class)//navigator
);
}
@Test
public void testSubmitCode(){
viewModel.verificationCode.set(VERIFICATION_CODE);
viewModel.submitCode();
assertTrue(viewModel.loading.get());
verify(accountRepository).verifyMobileNumber(
eq(unverifiedUser),//line 132
eq(VERIFICATION_CODE),//line 133
eq(CAMPAIGN_SOURCE),//line 134
verifyMobileNumberCallbackCaptor.capture());//line 135
UnverifiedUser.Entity entity = mock(UnverifiedUser.Entity.class);
when(entity.getId()).thenReturn(ENTITY_ID);
verifyMobileNumberCallbackCaptor.getValue().onVerificationSuccess(entity);
assertFalse(viewModel.loading.get());
assertEquals(viewModel.loginCredentials.get().getUsername(),UNVERIFIED_USER_EMAIL);
assertEquals(viewModel.loginCredentials.get().getPassword(),UNVERIFIED_USER_PASSWORD);
verify(navigator).finishActivity(true);
}
}
When I verify that the accountRepository.verifyMobileNumber
is invoked, I get the following error:
org.mockito.exceptions.misusing.InvalidUseOfMatchersException: Invalid use of argument matchers! 0 matchers expected, 3 recorded: -> at ...testSubmitCode(VerificationCodeViewModelTests.java:132) -> at ...testSubmitCode(VerificationCodeViewModelTests.java:133) -> at ...testSubmitCode(VerificationCodeViewModelTests.java:134)
This exception may occur if matchers are combined with raw values: //incorrect: someMethod(anyObject(), "raw String"); When using matchers, all arguments have to be provided by matchers. For example: //correct: someMethod(anyObject(), eq("String by matcher"));
For more info see javadoc for Matchers class.
at ...VerificationCodeViewModelTests.testSubmitCode(VerificationCodeViewModelTests.java:135)
What I do not understand is why does it say 0 matches expected? Other answers suggest replacing eq(..)
with any(...)
or isA(..)
.
Firstly, I don't believe that's applicable because the error is that no matchers are expected in the first place;
and secondly, I've tried it and the issue persists.
I'd be grateful if someone could explain why 0 matchers are expected and how to fix this problem.
Update
The implementation of AccountRepository.verifyMobileNumber()
is:
AccountRepository.java
public class AccountRepository implements AccountDataSource {
@Override
public void verifyMobileNumber(@NonNull UnverifiedUser unverifiedUser,
@NonNull String verificationCode,
@Nullable String sourceCampaign,
@NonNull VerifyMobileNumberCallback callback) {
this.remoteSource.verifyMobileNumber(unverifiedUser, verificationCode, sourceCampaign, callback);
}
}
AccountRemoteDataSource.java
public class AccountRemoteDataSource implements AccountDataSource {
@Override
public void verifyMobileNumber(@NonNull UnverifiedUser unverifiedUser,
@NonNull String verificationCode,
@Nullable String sourceCampaign,
@NonNull final VerifyMobileNumberCallback callback) {
accountService().verifyMobileNumber(/*params*/).enqueue(new Callback() {
@Override
public void onResponse(Response response, Retrofit retrofit) {
try{
//parse response
callback.onVerificationSuccess(entity);
} catch (Exception e) {
callback.onVerificationFailure(e.getMessage());
}
}
@Override
public void onFailure(Throwable t) {
callback.onVerificationFailure(t.getMessage());
}
});
}
}
Ahahaha, found it! You're using a @Mock ArgumentCaptor
by mistake in the sixth annotated field of your tests file.
@Mock private ArgumentCaptor<AccountDataSource.VerifyMobileNumberCallback>
verifyMobileNumberCallbackCaptor;
Mockito hasn't special-cased its own infrastructure, so it hasn't caught the fact that you're trying to mock Mockito itself. By calling the method ArgumentCaptor.capture()
in the middle of the verify
call, Mockito assumes that you're actually trying to verify the call to capture
.
Despite its clever syntax, Mockito is really just a state machine, where the call to verify(...)
starts a verification, each call to a matcher pushes a matcher description onto an internal stack, and then the next call to a Mockito mock triggers the verification. Mockito sees three matchers on the argument matcher stack for the zero-arg call to capture
. That's why there are 3 matchers recorded, and 0 expected.
Switch that annotation to @Captor
and you should be good to go.