Search code examples
unit-testingmockingmockitojunit5

Junit5 and Mockito: when() requires an argument which has to be 'a method call on a mock'


Can someone please explain.

I have a implementation class that looks like this:

public class CustomerServiceImpl implements CustomerService {
    
    @Override
    public List<Customer> addCustomerToCashier(Customer randomCustomer, List<Customer> peopleInStore) {
        peopleInStore.add(randomCustomer);
        return peopleInStore;
    }
}

I want to test it so I did next:

class CustomerServiceImplTest {

    private static CustomerServiceImpl customerServiceImpl;

    @BeforeAll
    static void setup(){
        customerServiceImpl = new CustomerServiceImpl();
    }

    @Test
    void addCustomerToCashier() {
        //given
        var randomCustomer = new Customer(UUID.randomUUID().toString(),"Leon", 11, 7);
        var randomPeople = new ArrayList<Customer>();
        randomPeople.add(randomCustomer);

        //when && then
        Mockito.when(customerServiceImpl.addCustomerToCashier(randomCustomer, randomPeople)).thenReturn(randomPeople);
    }
}

When I try to run this test I get following exception:

org.mockito.exceptions.misusing.MissingMethodInvocationException: 
when() requires an argument which has to be 'a method call on a mock'.
For example:
    when(mock.getArticles()).thenReturn(articles);

When I mock customerServiceImpl class or change my test code to this:

class CustomerServiceImplTest {

    private static CustomerServiceImpl customerServiceImpl;

    @BeforeAll
    static void setup(){
        customerServiceImpl = Mockito.mock(CustomerServiceImpl.class);
    }

    @Test
    void addCustomerToCashier() {
        //given
        var randomCustomer = new Customer(UUID.randomUUID().toString(),"Leon", 11, 7);
        var randomPeople = new ArrayList<Customer>();
        randomPeople.add(randomCustomer);

        //when && then
        Mockito.when(customerServiceImpl.addCustomerToCashier(randomCustomer, randomPeople)).thenReturn(randomPeople);
        assertEquals(1, randomPeople.size());
    }
}

test goes trough and its okay.

My question is why do I have to mock CustomerServiceImpl class? Is it because it does not have any other dependency for example like repository?

Because if my CustomerServiceImpl would have dependency like repository I would mock repository but I would not mock CustomerServiceImpl.

Basically if I would have something like this:

public class CustomerServiceImpl implements CustomerService {

    private final QueueRepository queueRepository;

    public CustomerServiceImpl(QueueRepository queueRepository) {
        this.queueRepository = queueRepository;
    }
....
}

In test class I would do something like:

class CustomerServiceImplTest {

    private static CustomerServiceImpl customerServiceImpl;
    private static QueueRepository queueRepository;

    @BeforeAll
    static void setup(){
        queueRepository = Mockito.mock(QueueRepository.class);
        customerServiceImpl = new CustomerServiceImpl(queueRepository);
    }

So for exception that is thrown by Mockito, my question is, why do I need to mock CustomerServiceImpl in first example and why I do not have to mock it in second example?


Solution

  • My question is why do I have to mock CustomerServiceImpl class? Is it because it does not have any other dependency for example like repository?

    Your aim is to test the real codes of CustomerServiceImpl.So it makes no sense you test its mock. Always create a real instance for the system under test (SUT) (i.e. CustomerServiceImpl). You only mock the dependencies of SUT (i.e. mock QueueRepository inside CustomerServiceImpl) in order to verify if SUT interacts correctly with its dependencies such as if the expected methods on dependencies are executed with expected parameter etc.

    And for why you get the following exception in the 1st example

    org.mockito.exceptions.misusing.MissingMethodInvocationException: when() requires an argument which has to be 'a method call on a mock'. For example: when(mock.getArticles()).thenReturn(articles);

    It is very natural because Mockito requires you to input a mock when calling Mockito.when() but now you are inputting an actual instance.

    In short , rewrite the test to :

    class CustomerServiceImplTest {
    
        private static CustomerServiceImpl customerServiceImpl;
    
        @BeforeAll
        static void setup(){
            customerServiceImpl = new CustomerServiceImpl();
        }
    
        @Test
        void addCustomerToCashier() {
    
            //given
            var randomCustomer = new Customer(UUID.randomUUID().toString(),"Leon", 11, 7);
            var randomPeople = new ArrayList<Customer>();
            randomPeople.add(randomCustomer);
    
           List<Customer> result = customerServiceImpl.addCustomerToCashier(randomCustomer, randomPeople);
    
           assertEquals(1, result.size());
    
        }
    }