Using Microsoft Fakes to test code that sends email

In the past I used to use Neptune to test any code that sent email. Neptune is still very useful with Web Test but with unit testing I should be able to use Fakes.

Below is a typical block of code that uses the SmtpClient class to send an email message. 

public static void SendMessage(string subject, string body, string to)
{
   
try
   {
      var server = ConfigurationManager.AppSettings["MailServer"];
      var serverPort = ConfigurationManager.AppSettings["MailServerPort"];
      var password = ConfigurationManager.AppSettings["MailServerPassword"];
      var username = ConfigurationManager.AppSettings["MailServerUsername"];
      var fromAddress = ConfigurationManager.AppSettings["MailServerEmail"];

      if (string.IsNullOrEmpty(fromAddress))
      {
         fromAddress = "info@didiwinthelotto.com";
      }

      var msg = new MailMessage(new MailAddress(fromAddress, "Did I Win the Lotto"),
                                new MailAddress(to))
                                {
                                   IsBodyHtml = true,
                                   Priority = MailPriority.High,
                                   Subject = subject,
                                   Body = body
                                };

      var smtp = new SmtpClient(server);
      if (!string.IsNullOrEmpty(password))
      {
         smtp.Credentials = new NetworkCredential(username, password);
      }

      if (!string.IsNullOrEmpty(serverPort))
      {
         int port;
         if (int.TryParse(serverPort, out port))
         {
            smtp.Port = port;
         }
      }

      smtp.Send(msg);
   }
   catch (Exception e)
   {
      var msg = string.Format("Could not send message to {0}\r\n{1}", to, e);
      WriteToEventLog(msg, EventLogEntryType.Error);

      if (e.InnerException != null)
      {
         WriteToEventLog(e.ToString(), EventLogEntryType.Error);
      }
   }
}

Of course I could refactor this code to use a generic interface for sending messages but I want to test the code as it was originally written.  One of the most powerful features of fakes is the ability to detour concrete classes using Shims. Shims will allow you to unit test legacy code in isolation without having to refactor the code first.

If you are not familiar with Microsoft Fakes I would suggest you first watch these short getting started videos before you continue http://tinyurl.com/MSFakesIntro.

If you have an app.config file in your test project with the correct AppSettings values you don’t have to fake the calls to ConfigurationManager.AppSettings.  But for a learning opportunity we are going to fake those as well.

First we need to create a Fakes Assembly for System.dll where SmtpClient is defined and System.configuration.dll for ConfigurationManager.  Expand the references folder of your test project and right click on System.dll and select Add Fakes Assembly. Now repeat for System.configuration.dll. A Fakes folder will be added to your project with a mscorlb.fakes, System.fakes and System.configuration.fakes files in it.  By default Fakes attempts to create Shims and Stubs for every type in each assembly which can lead to warnings and increase your build time.  We are going to override this default behavior and only fake the classes we need.

Open the mscorlib.fakes file and replace is contents with the following xml:

<Fakes xmlns="http://schemas.microsoft.com/fakes/2011/" Diagnostic="true">
   <Assembly Name="mscorlib" Version="4.0.0.0" />
   <StubGeneration>
      <Clear />
   </StubGeneration>
   <ShimGeneration>
      <Clear />
   </ShimGeneration>
</Fakes>

Now replace the contents of System.fakes with:

<Fakes xmlns="http://schemas.microsoft.com/fakes/2011/" Diagnostic="true">
   <Assembly Name="System" Version="4.0.0.0"/>
   <StubGeneration>
      <Clear />
   </StubGeneration>
   <ShimGeneration>
      <Clear />
      <Add FullName="System.Net.Mail.SmtpClient"/>
   </ShimGeneration>
</Fakes>

Finally replace the contents of System.configuration.fakes with the following:

<Fakes xmlns="http://schemas.microsoft.com/fakes/2011/" Diagnostic="true">
  <Assembly Name="System.configuration" Version="4.0.0.0"/>
   <StubGeneration>
      <Clear/>
   </StubGeneration>
   <ShimGeneration>
      <Clear/>
      <Add FullName="System.Configuration.ConfigurationManager"/>
   </ShimGeneration>
</Fakes>

This will make sure Fakes only creates Shims for the two classes we need.

When you are using Shims you must create a ShimsContext so let’s begin with the boilerplate code for using Shims.

[TestMethod]
public void Util_SendMessage()
{
   using (ShimsContext.Create())
   {
      // Arrange
      // Act
      // Assert
   }
}

Now we need to use the ShimConfigurationManager class created by Fakes to detour all the calls to AppSettings.  AppSettings returns a NameValueCollection so we are going to do the same, prepopulated with the needed values for our test.  Fakes has added a Fakes namespace to the System.Configuration namespace that has all the generated fake types.  Fakes uses delegates to allow the test to provide its own implementation of the property or function.  For AppSettingsGet we simply provide an anonymous function that returns a NameValueCollection.

The only other call we need to detour is the Send call of SmtpClient.  We have two options. We could detour the constructor of the SmtpClient class so that each time one is created during the execution of our test we get to give our implementation. This method I feel requires a lot more work and instead we are going to take the second option and detour the Send method for all instances of SmtpClient.  When Fakes generated the Shim for us it also implemented an AllInstances property that allows us to provide a custom implementation for all instances of a particular type.  The delegate that we provided for Send will be passed two parameters.  One which is the actual instance of the SmtpClient class that the call is being made on and the message to be sent.  Within this delegate we can use the Assert class to test any conditions we like.  However, if the code under test never calls the Send method our test may pass in error because none of the Asserts defined in the Send delegate would be tested. So we set a Boolean value to true when the Send delegate is called so we can verify that we actually sent the email. 

Once you put it all together your test will look like this:

 [TestMethod]
 public void Util_SendMessage()
 {
    using (ShimsContext.Create())
    {
       // Arrange
       System.Configuration.Fakes.ShimConfigurationManager.AppSettingsGet =
         () => new NameValueCollection {{ "MailServer", "127.0.0.1" },
                                        { "MailServerEmail", "info@diwtl.com" },
                                        { "MailServerPort", "25" },
                                        { "MailServerPassword", "unittest" },
                                        { "MailServerUsername", "info@diwtl.com" }};

      var msgSent = false;
      System.Net.Mail.Fakes.ShimSmtpClient.AllInstances.SendMailMessage =
        (client, message) =>
      {
         msgSent = true;
         Assert.AreEqual("unitTest@nowhere.com", message.To[0].Address);
      };

      // Act
      
Util.SendMessage("Testing", "This is a test", "unitTest@nowhere.com");

      
// Assert
      Assert.IsTrue(msgSent);
   }
}

As you can see Fakes is an extremely flexible and powerful Isolation framework.

Comments are closed