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.
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.
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.
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.
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.
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:
Customer
instance from the customerService
, but we don't need to do any sort of validation on that instance.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
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With