Unit-testing – How to not test implementation when method returns void

mockingrefactoringunit testing

Many places in stackexchange state that you should not unit test implementations, only the public interface of a class. But what if the public interface is a method that doesn't return any value, for example a public SendMailNotifications() method with certain logic that has some internal logic (private methods) and sends different recipients emails – I'd like to add tests that email is sent to the correct recipients depending on my business logic.

The only way I could think of is to convert the private methods to protected and then Verify() that they're called. But this seem to contradict the 'spirit' of unit tests where you shouldn't test implementations\private methods (and to convert the private methods to protected only for the sake of testing).

Is there any better way (a way which is more in the 'spirit' of unit tests) to accomplish this? Perhaps the code itself should be rafactored for this purpose? If so, how?

Best Answer

Dependency Injection and mocking is your friend. Whenever you find something hard to test, it usually means your class is having too many responsibilities. I'm assuming you are sending your emails using some library. That library most likely takes your message info (destination address, subject, body). What you want to do, is decouple your business logic that's in charge of collecting all email addresses, email subject and body, from that 3rd party library. How? By abstracting that library using an interface, and then injecting it into your Email class.

IEmailLibrary
{
   void SendEmail(EmailInfo emailInfo);
}

class Email
{
   private IEmailLibrary _emailLibrary;

   public Email(IEmailLibrary emailLibrary)
   {
      _emailLibrary = emailLibrary;
   }

   public void SendEmailNotifications()
   {
      EmailInfo emailInfo = ComposeEmail(); 
      _emailLibrary.SendEmail(emailInfo);
   }

   private EmailInfo ComposeEmail()
   {
      //your business logic
   }
}

Now in your production code, you would create a real EmailLibrary like this:

public RealEmailLibrary : IEmailLibrary
{
   ...

   public SendEmail(EmailInfo emailInfo)
   {
      _3rdPartyEmailComponent.SendEmail(emailInfo....);
   }
}

and then inject it into your Email class, like this:

RealEmailLibrary realEmailLibrary = new RealEmailLibrary();
Email email = new email(realEmailLibrary);

And if you wanted to test your business logic, all you need to do in your test code, is inject either a mock, or your own stub that implements an IEmailLibrary interface. For example:

public FakeEmailLibrary : IEmailLibrary
{
   public EmailInfo _iWasCalledWithThisEmailInfo;

   public void SendEmail(EmailInfo emailInfo)
   {
      _iWasCalledWithThisEmailInfo = emailInfo;
   }
}

You inject it in your test like this:

FakeEmailLibrary fakeEmailLibrary = new FakeEmailLibrary();
Email email = new email(fakeEmailLibrary);
email.SendEmailNotifications();
AssertStuff(fakeEmailLibrary.iWasCalledWithThisEmailInfo, expectedEmailInfo);

I recommend using mocking frameworks, as opposed to my stub example (like Moq for C#), which make it really easy to verify your injected dependencies were called with the right arguments.