Juri Strumpflohner
Juri Strumpflohner Juri is a full stack developer and tech lead with a special passion for the web and frontend development. He creates online videos for Egghead.io, writes articles on his blog and for tech magazines, speaks at conferences and holds training workshops. Juri is also a recognized Google Developer Expert in Web Technologies

Test-Driven-Design: A real world scenario of an email sending requirement

6 min read

Many applications have requirements to send automatic emails to their users, especially in web environments. So it was also in my case.

Summarizing in a few words, the requirement was the following:
Automatically send an email notification to the company coordinator as well as to the admin (in CC) in case of a rectification of an already transmitted order (let's call it this way).
The content of the email subject as well as of the message body depends on the kind of order that has been transmitted previously.
So, the approach is to have some kind of email-message templates where the specific gaps are filled with the necessary information. Such an email body could look like
Dear [company_coordinator_name],

[username] has requested to rectify the order with number [ordernumber] which has been transmitted at [transmission_data].


Kind regards
Obviously this is just a fake message content, but it's enough to understand the requirement's context.

So when you think about implementing this, you would probably place the message template in a resource file, substitute all "[placeholders]" with {0},{1},...,{n} s.t. you can later replace them by using String.Format(messagebody, value_1, value_2,...,value_n). So far so good, then you'd create a class which sends the mail by using the .Net available SMTPClient and MailMessage classes and - of course - assuming you dispose of a SMTP server that allows you to perform the operation.
So a first draft could look as follows (don't take the UML too strictly, it's just for better understanding)

Pretty simple, what's your opinion? Be aware!! The complexity is hidden here. Remember the logic of variations in the email content depending on the order type? There will be a couple of if-statements which type of order is being processed and accordingly the text will be adapted, data will possibly be fetched from the DB and so on. What are the problems with that? Take these...
  • Where will this logic reside? In the OrderBL or within EmailService?
    On the one hand it could be within the OrderBL because there's where the order data is known and handled. But wait, loading and preparing the email template there? Should the OrderBL really know about that stuff? Know where to fetch the email text? Would be better within the EmailService then, right? But that means the EmailService's SendMail(...) will take quite a lot of parameters (think of your template having 10+ placeholders).

  • Extensibility: What about when a new Order type comes in?
    What would that mean for our solution? Depending on where you placed your logic, touch the OrderBL, EmailService and so on, add new if statements to handle the new order type. Bad!

  • Reusability?
    The EmailService class may come in quite handy since within another project there's also the need to send emails. But there I have different kind of requirements, different data and consequently different email templates! Not reusable!

  • Finally, testability?
    How to you test it? You surely don't want to test the email functionality itself. That's out of scope of a unit test, but you'd like to verify the correctness of the produced (and ultimately send) email body, subject etc...Verifying EmailService.SendMail(...) will be difficult, given the strict references to SMTPClient and MailMessage.
So can we come up with a better design in terms of the above mentioned points?

What I did in my situation is to not think about pure functionality but about how to test it. List down the facts which would make your design more easily testable:
  • F1: No control over SMTPClient and MailMessage
    They are somehow in the way. Direct references to them, especially to SMTPClient will result in sending mails somewhere which we definitely don't want during Unit-Testing.

  • F2: Clearly separate responsibilities
    A class having multiple responsibilities results in more if-statements and thus higher complexity (see CC). This not only lowers it's maintainability but also hinders us from being able to test it.

  • F3: Decouple
    Remember the purpose of interfaces? This is essential but still, many developers don't really get the reason for using interfaces. First clearly define the contracts, then implement the functionality.

Let's go stepwise.
  1. We have the concept of templates, so let's create an interface IMailTemplate which knows the stuff a mail template has to expose to the world, basically the subject and mail body.

  2. The concept of a email sender. This class should just get the addresses, subject and body. Then it should care about sending the mail (somehow).

  3. The email sending service. It should know where to fetch the email addresses and to delegate the sending of the template to its destination. It shouldn't care about how the sending takes place.

  4. Finally, the already in our application existing OrderBL. Its sole responsibility in the term of the requirement is to know how to create a specific email template and in which state of the application the email has to be sent.
So for the first point we have the following situation.

A generic template interface and the concrete implementations for the different types, just as it gets taught in any programming class :)

In the case of the mail sender concept we have an interface IMailSender and a concrete implementation. Note, this separates out the dependencies to the underlying .Net classes.

Let's come to the email service. It takes a template, and has a mail sender associated (through the IMailSender interface). Again, note that the email service has no knowledge on how the email gets send. A concrete implementation of the IMailSender could also just write a file on the FS :)

And finally the OrderBL which simply knows about a concrete email template and a email service which can do something with that template. That's it.

I know what you think now. Damn that got much more complex, look at how many interfaces, classes etc there are. Wait, give me a chance to highlight you the differences and explain them.
It's true, there are more classes, but it became simpler in terms of complexity, maintainability and testability. Before it was just hidden in large if statements within an OrderBL or EmailService class.

Let's revise the 4 points I mentioned in the very beginning against the new design:
  • Where will this logic reside? In the OrderBL or within EmailService?
    Simple, isn't it? Each has it's clear responsibility, OrderBL knows how to create a template and gives its associated EmailService the command to process it.

  • Extensibility: What about when a new Order type comes in?
    Again, too simple. Just create another concrete implementation of a template, MailTemplateOrderTypeC. That's it, nothing more and nothing less, everything will work without touching other code.

  • Reusability?
    Reusing the functionality of sending a mail? No problem, take the EmailSender class with interface and you're done. It can be reused in any arbitrary context.

  • Finally, testability?The master discipline, will it be testable? Sure, you could now test the EmailService class by passing it an IMailTemplate of your choice and by mocking out the undesirable IMailSender object.
Still not convinced? Well :) it takes time, took me a while actually to think in this way and to understand it.

The reason why I'm not going to post the underlying code (unit tests and classes) is that the purpose of this post is not to show about the technical details, but to emphasize the impact of TDD onto the design of your software. That's where the often forgotten meaning Test-Driven-Design comes from.

Hope you got the point. Feel free to leave any comment :)

The art of unit testing is not about testing, but about the art of writing testable code.

Questions? Thoughts? Hit me up on Twitter
comments powered by Disqus