Search code examples
androidmockitomatcher

"Mockito 0 matchers expected, 3 recorded" - Why are 0 matchers expected?


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());
            }
        });
    }
}

Solution

  • 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.