Testing applications that communicate using email is more challenging than e.g. testing database access. Thanks to GMail and Spring, it can be done pretty easily.

Here is the scoop: I need to generate email notifying users about business events and after sending the email, store the content in the database. In real environment you will use corporate SMTP server and real email addresses of real people.
For development we can avoid bothering and spamming our customers by few simple tricks.

First step is to get new GMail account. Name it e.g. Company.Notifier or something easily distinguishable. In Spring, configure the sender bean:

[sourcecode language='xml']

smtp.gmail.com

true

true
25000

company.notifier

secret


[/sourcecode]
The port can have value 25 (default) - but if you are using ISP provider such as Rogers, chances are the default port is blocked for outgoing connections - you can use port 587 instead.Second piece is component that actually uses the mailSender: notificationService.
[sourcecode language='xml']

miro.adamy+alwaysCC@thinknostic.com

.... deleted ...

[/sourcecode]
Note the velocityEngine bean that is used to generate the body of the email from the template. The 'alwaysCCList' property is using nice little trick available with GMail: if you send email to YOURNAME+anything@gmail.com, the '+anything' will be ignored but kept with email and it will arrive as if the address were just YOURNAME@gmail.com. You can use the postfix to find or autotag the emails.The code that sends email is actually pretty simple (the method of the notificationService)
[sourcecode language='java']
public EmailContent sendNotificationEmail(EmailParameters params, EmailContent _content, boolean isDryRun) {

final EmailContent c = mergeTemplate(params, _content);

MimeMessagePreparator preparator = new MimeMessagePreparator() {
public void prepare(MimeMessage mimeMessage) throws Exception {
MimeMessageHelper message = new MimeMessageHelper(mimeMessage);
message.setTo(c.getEmailTo());
message.setFrom("DO-NOT-REPLY@company.com"); // could be parameterized...

message.setText(c.getEmailBody(), true);
message.setSubject(c.getEmailSubject());

if (alwaysCCList != null && alwaysCCList.size() > 0) {
message.setCc(alwaysCCList.toArray(new String[0]));
c.setEmailCcFromList(alwaysCCList);
}
...
}
};

if (isDryRun || emailsToFile)
{
// save to file
...
}

if (!isDryRun)
this.mailSender.send(preparator);
return c;
}

[/sourcecode]
The class EmailContent is container for email address, subject, body, CC list.
It gets created as empty class with only recipient name and email address passed from business method as well as name of the Velocity template that is used to render email body. The method mergeTemplate loads the Velocity template and renders the actual email body, using the parameters (which is more or less) a hash map, cotaining e.g. URL's or information that needs to be inserted to email. The generated content is stored back to EmailContent, which will be after successful delivery written to database for audit and archiving purposes.If you are JavaScript or Ruby programmer, you will immediately recognize the 'functor' pattern: preparator is set to instance of anonymous inner class and is used with configured preparator.The actual rendering of the content using Velocity can be done like this:

[sourcecode language='java']

private EmailContent mergeTemplate( EmailParameters params, EmailContent content) {
Map model = new HashMap();
model.put("param", params);
model.put("content", content);

String text = "MISSING TEXT";
String subject = "Notification email";
String template = content.getTemplateId();
try {

// get subject line

if (template_names.containsKey(template)) {
subject = template_names.get(template);
VelocityContext velocityContext = new VelocityContext(model);
StringWriter writer = new StringWriter ( ) ;
PrintWriter out = new PrintWriter ( writer ) ;

Velocity.evaluate(velocityContext, out, "subject", new StringReader(subject));
subject = writer.toString();

model.put("subjectLine", subject);
// now the body
text = VelocityEngineUtils.mergeTemplateIntoString(velocityEngine,
"com/thinknostic/APP/service/email/"+template, model);

}
else {
// TODO: log configuration error - template not found
}

content.setEmailBody(text);
content.setEmailSubject(subject);
return content;

} catch (VelocityException e) {
// back to untranslated
// TODO: error report
// subject = params.subject;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

return null;
}

[/sourcecode]
In the above, template id is actual file name with body of the HTML email and the hashmap template_names (configured in Spring XML) maps the id to another string, which is then used as subject line. Both body and subject can contain macros.
Note: if you get conflict on ${} syntax, see this entry.

Now, finally - how do we test this ? It is quite easy, thanks to JUnit support in Spring.

[sourcecode language='java']

@ContextConfiguration(locations={"/com/thinknostic/APP/test/test-context.xml",
"/com/thinknostic/APP/test/service/NotificationTests-context.xml"})
public class NotificationTests extends AbstractTransactionalJUnit4SpringContextTests {

@Autowired
NotificationServiceImpl notificationService;
public NotificationServiceImpl getNotificationService() {
return notificationService;
}
public void setNotificationService(NotificationServiceImpl notificationService) {
this.notificationService = notificationService;
}

@Autowired
ApprovalDAO approvalDao;
public ApprovalDAO getApprovalDao() {
return approvalDao;
}
public void setApprovalDao(ApprovalDAO approvalDao) {
this.approvalDao = approvalDao;
}

Document doc;
EmailContent cont;
Approval app;
ApprovalStep step1;
ApprovalStep step2;

@Before
public void createMockApproval() {
this.logger.info("Before");
// controlls whether to use emails
notificationService.setReallySend(true);

doc = (Document)DocumentInfo.createMockDI("103", 1);

// create the approval and insert it into
app = Approval.createMockApproval(1L, doc);
approvalDao.insert(app);

step1 = ApprovalStep.createMockStep(app.getApprId(), 1);
approvalDao.insert(step1);
step2 = ApprovalStep.createMockStep(app.getApprId(), 2);
approvalDao.insert(step2);

cont = EmailContent.createMock(app.getApprId(), step1.getStep(), "miro adamy", "miro_adamy@rogers.com");

}

@After
public void cleanup() {
this.logger.info("After");
// reset
notificationService.setReallySend(false);
doc = null;
cont = null;
app = null;
step1 = null;
step2 = null;
}

private void assertEverything()
{
assertTrue("The email service is empty", notificationService != null);
assertTrue("The email parameteres not available", emailParameters != null);
assertTrue("The document does not exist", doc != null);
}

@Test
public void approval_all_cancelled_test() {
doc.setFilename("approval_all_cancelled");

notificationService.notifyApprovalCancelledAll(doc, cont);
}

@Test
public void approval_cancelled_test() {
assertEverything();

doc.setFilename("approval_cancelled");
EmailContent ui = EmailContent.createMock(app.getApprId(), step1.getStep(), "miro adamy", "Miro.Adamy@gmail.com");

notificationService.notifyApprovalCancelled(doc, ui);
}

// and so on - many more tests
....
}

[/sourcecode]
Assumed that your Spring context configuration files defined in annotations are OK, all you have to do is run the suite. The XML defines beans with names corresponding to the test properties and autowiring will take care of the rest.Also notice that @Before method (which runs before each test) actually DOES modify the database and inserts records with same primary key over and over. This works thanks to transaction magic of Spring, which rolls back the inserts at the end of the test.Last thing to mention is using of "mockXXX" static methods to generate the instance of object. IT not really a mock object we are creating here, but a well defined instance of business object with some properties parametrized. You can even define it as a bean in Spring context XML, but that is IMHO an overkill - keeping everything in test Java class makes things easier to understand.

This method does belong to tests, but it is very convenient just to keep it with domain object. I usually create the 'createMock' method right after I add a new domain object