Yesterday I realised yet again the importance of writing tests ahead of code. I was putting together the "feedback" functionality on my website (www.jemos.co.uk) so I wrote all the backend stuff (you know? That which sends emails etc) thoroughly tested and that was fine.
The Mail Service method signature looked like this one:
public void sendMail(InternetAddress from, Set<InternetAddress> to,
Set<InternetAddress> cc, Set<InternetAddress> bcc, String subject,
StringBuilder message)
Now it was time to write the Spring Command Controller to manage the feedback data sent from the user. And here I made my mistake: since I wanted to see the service working ASAP, I started implementing the code without writing the tests first, and this was the result:
@RequestMapping(method = RequestMethod.POST)
public String processSubmit(
@ModelAttribute(JemosWebConstants.REQ_ATTR_NAME_FEEDBACK_DTO)
FeedbackDto feedbackDto,
BindingResult result, HttpServletRequest request) {
InternetAddress from =
JemosServicesUtils
.getMailRecipientFromString("marco.tedone@googlemail.com");
Set emailAddresses = new HashSet();
emailAddresses.add("marco.tedone@googlemail.com");
Set to =
JemosServicesUtils.getMailRecipientsFromStrings(emailAddresses);
String subject = "[Jemos Web] - Feedback received from user";
StringBuilder messageBuf = new StringBuilder();
messageBuf.append("User full name: ").append(feedbackDto.getName())
.append(JemosConstants.NEW_LINE).append("User email address: ")
.append(feedbackDto.getEmailAddress()).append(
JemosConstants.NEW_LINE).append("User message: ")
.append(feedbackDto.getMessage());
mailService.sendMail(from, to, null, null, subject, messageBuf);
LOG.info("Feedback message sent via email");
String feedbackMsg = "Thank you! Your feedback has been sent.";
request.setAttribute("feedbackMessageText",
feedbackMsg);
return "contactUs";
}
I then updated the JSP page and the CSS until I obtained the desired effect. Then, before committing, I had to write the tests for the Controller and at that point two things happened:
1) I felt tired of coding and there was one voice in the back of my head saying me..."What the hell, the code works, you tested the application just now, can't we skip testing just for this one time?".
I resisted this temptation and I started writing the Controller unit tests, and at this point the second thing happened:
2) I suddenly realised that much of the code I wrote in the controller method was not testable because all those hard-coded constants for email addresses email subject, view names, etc
From the moment I started coding as by magic I started refactoring all those constants in a non instantiable JemosWebConstants class because otherwise I couldn't state my expectations.
The resulting controller method changed as follows:
@RequestMapping(method = RequestMethod.POST)
public String processSubmit(
@ModelAttribute(JemosWebConstants.REQ_ATTR_NAME_FEEDBACK_DTO)
FeedbackDto feedbackDto,
BindingResult result, HttpServletRequest request) {
InternetAddress from =
JemosServicesUtils
.getMailRecipientFromString(JemosWebConstants.MAIL_FROM_ADDRESS);
Set emailAddresses = new HashSet();
emailAddresses.add(JemosWebConstants.MAIL_FROM_ADDRESS);
Set to = JemosServicesUtils
.getMailRecipientsFromStrings(emailAddresses);
String subject = JemosWebConstants.FEEDBACK_EMAIL_SUBJECT;
StringBuilder messageBuf =
JemosWebUtils.createFeedbackMessageBuffer(feedbackDto);
mailService.sendMail(from, to, null, null, subject, messageBuf);
LOG.info("Feedback message sent via email");
String feedbackMsg = JemosWebConstants.FEEDBACK_ACKNOWLEDGEMENT_MSG_TEXT;
request.setAttribute(JemosWebConstants.REQ_ATTR_NAME_FEEDBACK_MSG,
feedbackMsg);
return JemosWebConstants.VIEW_NAME_CONTACT_US;
}
Except from the log message content, do you see any hard-coding? Exactly!
The only annoying part of writing unit tests for Controllers with Spring >= 2.5 is the mocking of services and I'll explain myself; one of the advantages of using Spring 2.5 and beyond is the possibility to use annotations to define and inject service beans. Often these services provide interactions with other systems (email, database, etc) but when writing unit tests these services must be mocked. Now the usual way to mock the services is to create a mock (e.g. using EasyMock) and then set these mocks in the controller being tested and here comes the annoying part: since we should expose our services through their interfaces, the setter method for the service needs to be in the interface as well. But this setter is there for testing really, since in the real world we would use Spring autowiring injection instead.
This is what I called the Mocked Service Setter Antipattern.
Going back on track these are the conclusions of today's post: writing tests ahead (aka TDD) has got, amongst the others found in various literature, the following advantages:
1) By writing the tests first, developers will not fall into the temptation of not writing tests at all once all the code has been written and a first, manual, set of tests has been conducted successfully. TDD has also the indirect advantage of providing a 100% (or thereabouts) coverage for your code. In fact any line of production code exists only to fix a broken test
2) It will force you to refactor any line of code that needs to be reused by both your code and your tests (e.g. constants, common utility code, etc)
Of course TDD has got many other advantages, for which I invite you to document yourself both online and by reading the vast literature on the subject of Agile development and TDD
Recent Comments