I am new to practicing TDD and I am faced with a design problem in writing what seems a straightforward unit test.
The method under test does two things:
- Calls a private method that converts object A into object B
- Calls another private method passing object B as an argument
Something like this:
public void doStuff(A objectA) {
B objectB = convertToB(objectA);
processB(objectB);
}
Now, where do I test if the conversion was done correctly? The popular opinion says that using TDD there is no need in using PowerMock or other libraries to test private methods. I quote Practical Unit Testing with JUnit and Mockito book:
The first thing you could, and probably should, do, is to avoid such a situation. How? By following the TDD approach. Think about how private methods come to life when you code test first. The answer is, that they are created during the refactoring phase, which means their content is fully covered by tests (assuming that you really follow the TDD rules, and write code only when there is a failing test). In such cases there is no problem of an "untested private method which should somehow be tested", because such methods simply do not exist.
My next idea was to use an ArgumentCaptor and verify if processB()
was called with correct argument. But again, processB()
is private, too, so it cannot be done.
Of course, a lot of tricks can be done to make my class testable - save objectB
in a class field or make one of my private methods public. But that will worsen, not improve my design.
So, my questions in this case are: What is the correct way to test the conversion method? What design improvement can make this code testable?
EDIT: Adding a real-world example to give a better picture of the problem:
public class EmailSender {
public EmailResult send(Email email) {
MultivaluedMapImpl formData = prepareFormData(email);
EmailResult emailResult = processEmailRequest(formData);
}
private MultivaluedMapImpl prepareFormData(Email email) {
MultivaluedMapImpl formData = new MultivaluedMapImpl();
formData.add(FROM_KEY, email.getSender());
email.getRecipients().stream().forEach((recipient) -> {
formData.add(TO_KEY, recipient);
});
formData.add(SUBJECT_KEY, email.getSubject());
formData.add(TEXT_KEY, email.getText());
return formData;
}
private EmailResult processEmailRequest(MultivaluedMapImpl formData ) {
Client client = Client.create();
client.addFilter(new HTTPBasicAuthFilter("api", "API_KEY"));
WebResource webResource = client.resource(API_URL);
ClientResponse clientResponse = webResource.type(MediaType.APPLICATION_FORM_URLENCODED).
post(ClientResponse.class, formData);
String resultString = clientResponse.getStatusInfo().getFamily().toString();
EmailResult emailResult = resultString.equals("SUCCESSFUL") ? EmailResult.SUCCESS : EmailResult.FAILED;
return emailResult;
}
}
Here, prepareFormData()
corresponds to the conversion method in the previous example. What I try to test is if the conversion is correct.