Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 350
user5417542 Avatar asked Oct 29 '15 10:10

user5417542


People also ask

How do you stop a getter setter?

Thus: you avoid getters and setters by thinking in terms of behavior, not in terms of state. Getters/setters manipulate state, from the "outside" (by doing avail = purse.

Are getters and setters bad practice?

Getter and setter methods (also known as accessors) are dangerous for the same reason that public fields are dangerous: They provide external access to implementation details. What if you need to change the accessed field's type? You also have to change the accessor's return type.

Should setter methods be private?

Usually you want setters/getters to be public, because that's what they are for: giving access to data, you don't want to give others direct access to because you don't want them to mess with your implementation dependent details - that's what encapsulation is about.

Should setters be tested?

If you tests use the getters/setters to achieve their goal of testing the "real" functionality, then that's good enough. If, on the other hand, your getters and setters do more than just get and set (i.e. they're properly complex methods), then yes, they should be tested.


2 Answers

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
}
like image 70
Makoto Avatar answered Oct 11 '22 17:10

Makoto


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.

like image 43
Rogério Avatar answered Oct 11 '22 17:10

Rogério