mockito / mockito-kotlin

Using Mockito with Kotlin
MIT License
3.11k stars 202 forks source link

Unnecessary stubbing false detection #282

Closed BenoitDuffez closed 6 years ago

BenoitDuffez commented 6 years ago

Hello, I'm rather new to Kotlin and unit testing using mockito, se please bear with me and point me to the correct information if what I ask is dumb or badly explained.

I have the following test which passes:

@Test
fun pushSeveralCustomerSites() {
    // Prepare data
    val customer = Customer()
    customer.id = 1L
    customer.name = "name"
    val site = Site()
    site.id = 1L
    site.name = "site"
    customer.sites!!.add(site)
    site.customerId = customer.id!!

    // Given
    val cusRepo: CustomersRepository = mock { _ ->
        on { syncGetAllSyncableSites() } doReturn (listOf(site))
        on { setSitesSyncSuccess(anyArray<CustomerSite>().toList(), any()) }.then { } // line 175
    }
    val backend: Backend = mock { }
    val syncAdapter = buildSyncAdapter(mock(), backend, cusRepo, mock(), mock())

    // When
    syncAdapter.pushCustomerSites()

    // Then
    val captor = argumentCaptor<List<CustomerSite>>()
    verify(backend).pushCustomerSites(captor.capture())
    val syncedCustomers = captor.firstValue
    Assert.assertTrue(syncedCustomers.size == 1)
    Assert.assertEquals(syncedCustomers[0].name, site.name)
}

However if I run the whole test suite in that class, Mockito warns me:

org.mockito.exceptions.misusing.UnnecessaryStubbingException: 
Unnecessary stubbings detected in test class: SyncAdapterTest
Clean & maintainable test code requires zero unnecessary code.
Following stubbings are unnecessary (click to navigate to relevant line of code):
  1. -> at com.xxx.SyncAdapterTest$pushSeveralCustomerSites$cusRepo$1$2.invoke(SyncAdapterTest.kt:175)
Please remove unnecessary stubbings or use 'lenient' strictness. More info: javadoc for UnnecessaryStubbingException class.

Line 175 is highlighted. This is weird because the actual code under test is:

fun pushCustomerSites() {
    val sites = customersRepository.syncGetAllSyncableSites()

    if (sites.isEmpty()) {
        return
    }

    val syncSites = backend.pushCustomerSites(sites)
    customersRepository.setSitesSyncSuccess(sites, syncSites)
}

I have run it step by step, the setSitesSyncSuccess method from the customers repository mock is called. I have checked also that the customersRepository object is indeed the mock created in the test.

What's weirder is that when translated to java, the test passes and I don't have the warning from Mockito:

@Test
public void pushSeveralCustomerSites() {
    // Prepare data
    Customer customer = new  Customer();
    customer.setId(1L);
    customer.setName("Customer");
    site.setId(1L);
    site.setName("site");
    customer.getSites().add(site);
    site.setCustomerId(customer.getId());

     // Given
    CustomersRepository cusRepo = mock(CustomersRepository.class);
    List<CustomerSite> sites = new ArrayList();
    sites.add(site);
    when(cusRepo.syncGetAllSyncableSites()).thenReturn(sites);
    doNothing().when(cusRepo).setSitesSyncSuccess(any(List.class), any(ApiResponse.class));
    doNothing().when(cusRepo).deleteSite(any(CustomerSite.class));
    Backend backend = mock(Backend.class);
    SyncAdapter syncAdapter = buildSyncAdapter(mock(Context.class), backend, cusRepo, mock(QuestionnairesRepository.class), mock(AccountUtil.class));

    // When
    syncAdapter.pushCustomerSites();

    // Then
    ArgumentCaptor <List<CustomerSite>> captor =  ArgumentCaptor.forClass(List.class);
    verify(backend).pushCustomerSites(captor.capture());
    List< CustomerSite> syncedCustomers = captor.getValue();
    Assert.assertTrue(syncedCustomers.size() == 1);
    Assert.assertEquals(syncedCustomers.get(0).getName(), site.getName());
}

Is it related to this lib?

BenoitDuffez commented 6 years ago

Fixed it by replacing anyArray<CustomerSite>().toList() with any(). No idea why this changes how the mocks are called, because I did that change (from any()) because it complained about the type not being able to capture what the actual code is calling.