How to deal with Setter/Getter-Methods from Mocks?

37,379

Solution 1

Only mock the things which you cannot create or pass through yourself. Do not mock any passed-in entities; providing a fake version is often far superior.

In this scenario, we can get away with a couple of things since we know a few things about our test:

  • We are getting back a Customer instance from the customerService, but we don't need to do any sort of validation on that instance.
  • We have to mock the customerService out since it is an injected dependency.

In light of these two things, we should mock out CustomerService, which you do kind of successfully - since the field is named the same you don't need the extra metadata in the annotation.

@Mock
private CustomerService customerService;

You should also look to use the test runner provided with Mockito so you don't have to explicitly initialize the mocks.

@RunWith(MockitoJUnitRunner.class)
public class RestaurantTest {
    // tests
}

Now, on to the actual unit test. The only thing that really sucks is that you have to provide an instance of a Customer to use, but outside of that, it's not too bad. Our givens are the instance of CustomerData we want to mutate, and the Customer we're providing for test. We then have to simply assert the values that we care about are coming back for our test CustomerData instance.

@Test
public void updateCustomer_WithValidInput_ShouldReturnUpdatedInput(){
    //given
    final Customer customer = new Customer();
    customer.setId("123");
    customer.setAddress("Addr1");
    customer.setName("Bob");
    customer.setCity("St8")
    customer.setLanguage("Java");

    final CustomerInputData inputData = new CustomerInputData();
    inputData.setId(customer.getId());

    //when
    when(customerService.getCustomerById(customer.getId())).thenReturn(customer);
    classUnderTest.updateCustomer(customerData);

    //then
    verify(customerService.getCustomerById("123"));
    assertThat(customerData.getCustomerName(), equalTo(customer.getName()))
    // and so forth
}

Solution 2

That is not the "right" way to go about it, since mocking value objects is widely regarded as a bad practice (it even says so in Mockito's documentation).

Your test should instead look like this:

@Test
public void updateCustomer_WithValidInput_ShouldReturnUpdatedInput() {
    String customerId = "customerId";
    String name = "Name";
    String address = "address";
    String language = "language";
    Customer customer = new Customer();
    customer.setName(name);
    customer.setAddress(address);
    customer.setLanguage(language);
    CustomerInputData inputData = new CustomerInputData();
    inputData.setId(customerId);

    doReturn(customer).when(customerService).getCustomerById(customerId);

    CustomerInputData updatedInput = classUnderTest.updateCustomer(inputData);

    assertSame(inputData, updatedInput);
    assertEquals(name, updatedInput.getCustomerName());
    assertEquals(address, updatedInput.getCustomerCity());
    assertEquals(language, updatedInput.getLanguage());
}

For a good presentation on unit testing, see Martin Fowler's recent article.

Share:
37,379
user5417542
Author by

user5417542

Updated on October 30, 2020

Comments

  • user5417542
    user5417542 over 3 years

    So I am still having trouble with the usage of Mockito. So let's assume I have following class (Please ignore the logic, or structure of it, it's just a short example I created from another class, with different names and so on.) :

    public class Restaurant(
        @Autowired
        private CustomerService customerService;
    
        private CustomerInputData updateCustomer(CustomerInputData inputData){
            String customerId = inputData.getID();
            Customer customer = customerService.getCustomerById(customerID);
            if(customer.getAddress() != null){
                inputData.setCustomerName(customer.getCustomerName());
                inputData.setCustomerCity(customer.getCustomerCity);
                inputData.setCustomerLanguage(customer.getLanguage);
            }
    
            return inputData
        }
    }
    

    So my understanding of Unit-Tests is, to isolate all dependencies. Here I would have the Customer-class and the Customer-Service.

    So to write a test-class, I would currently do following:

    public class RestaurantTest()
    {
        @Mock(name="customerService");
        private CustomerService customerService;
    
        @InjectMocks
        private Restaurant classUnderTest;
    
        @Before
        public void setUp(){
            MockitoAnnotations.initMocks(this);
        }
    
        @Test
        public void updateCustomer_WithValidInput_ShouldReturnUpdatedInput(){
            //Some Mocking first
            String customerId = "customerId";
            Customer customer = mock(Customer.class);
            CustomerInputData = mock(CustomerInputData.class);
    
            doReturn(customer).when(customerService.getCustomerById(any(String.class)));
            doReturn(customerId).when(inputData.getId());
    
            doReturn("address").when(customer.getAddress());
            doReturn("Name").when(customer.getName());
            doReturn("City").when(customer.getCity());
            doReturn("Language").when(customer.getLanguage());
    
            doNothing().when(inputData).setCustomerName(any(String.class));
            doNothing().when(inputData).setCustomerCity(any(String.class));
            doNothing().when(inputData).setCustomerLanguage(any(String.class));
    
            verify(customer.getAddress(), atLeastOnce());
            verify(customer.getName(), atLeastOnce());
            //and so on...
    
            verify(inputData, atLeastOnce()).setCustomerName(eq("Name"));
            verify(inputData, atLeastOnce()).setCustomerCity(eq("City"));
            verify(inputData, atLeastOnce()).setCustomerLanguage(eq("Language");
    
        }
    }
    

    So currently I have no Assert, I only check if the right methods get called. The reason, why I try to do this like this, and not let the Test-class call the setter/getter is because of isolation. Let's assume inputData.setCustomerCity is broken, my test would fail. So it is depending on the CustomerInputData-Class.

    Now how do I approach these getter and setters, what is the best practice?

    Do I have not understood Mockito good enough? When I use mock(), can I then just use the setter-methods and gethods, and don't need to worry about using doReturns and so on?

    I know it is a whitebox-test, I know the methods and what's going on.

  • user5417542
    user5417542 over 8 years
    Ok, so basically, I should never mock anything that get's passed to a method, or is returned from it. I am asking, because for me it made sense to mock the Customer, since I don't care if the setId(), setAddress-methods and so on work, I just want to ensure, that they are called. But two things: I heard, that you should only use one assert per method. So shouldn't I do: assertEquals(customer, classUnderTest.updateCustomer(customerData))? And concerning the the verify, in your example you don't pass anything else like times(1) or so. Is this advised, or what does it do? Btw thanks for your help