25

I would like to test my email sending functionality using .NET (C#) framework or any compatible library, any suggestion how to do it?

рüффп
  • 4,475
  • 34
  • 62
  • 99
GigaPr
  • 4,788
  • 14
  • 55
  • 76

9 Answers9

33

If you need test sending e-mail only, you can configure your .config file like this

<system.net>
    <mailSettings>
        <smtp deliveryMethod="SpecifiedPickupDirectory">
            <specifiedPickupDirectory pickupDirectoryLocation="C:\TempMail" />
        </smtp>
    </mailSettings>
</system.net>

With these settings your messages are not sent over the network, but are dropped as physical files with .eml extension in the folder you configured in the pickupDirectoryLocation attribute. You can check them with the help of classes in the System.IO namespace.

The MSDN documentation is here

рüффп
  • 4,475
  • 34
  • 62
  • 99
whyleee
  • 3,839
  • 1
  • 28
  • 32
20

I disagree with the notion of actually sending an email during unit testing. That's asking for a lot of trouble IMO and goes against what unit testing is supposed to be about.

In my app, I have an IMailManager interface with methods like SendPasswordResetEmail(string emailAddress) and the like. I mock this object during unit testing and just make sure my components are calling the correct mail manager method.

My actual production implementation of MailManager typically uses System.Net.Mail.SmtpClient internally, which you don't need to test. Let Microsoft test that. All you need to do is ensure your smtp settings are set up correctly at deployment, which shouldn't be the concern of unit testing.

If you need to test your mail component itself, ie ensure that it's generating the correct message body and the like, I'd recommend mocking out what is needed to isolate that functionality into a self contained unit test.

Matt Greer
  • 57,161
  • 16
  • 118
  • 122
  • 2
    And how do you know if your SendPasswordResetEmail method is implemented properly? You don't. That is why you need to test it. True I probably won't call these unit test, but they need to be tested regardless. – Sleeper Smith Mar 13 '12 at 22:09
  • 7
    @SleeperSmith that's not a unit test, that's an integration test as it communicates with an outside system. This kind of test is needed but not in a unit test capacity. – CD Smith Jun 18 '12 at 13:56
  • 3
    The question was to test actual 'email sending functionality'. I'm pretty sure by 'unit test' author really had in mind a kind of integration test. – whyleee Aug 26 '12 at 01:56
13

You don't need to test the actual email sending feature; that's part of the .NET framework and it's already tested.

What you need to unit test is the email creation business logic. Encapsulate it in a service like this:

public interface IPasswordResetEmailCreator
{
    MailMessage Create(string emailAddress);
}

And implement it. Then write an unit test for this implementation and verify that the MailMessage it returns conforms to your requirements.

A sample implementation of this unit test using the SpecsFor framework:

public class PasswordResetEmailCreatorSpecs
{
    public class given_a_registered_user : SpecsFor<PasswordResetEmailCreator>
    {
        private string _emailAddress;
        private MailMessage _email;

        protected override void Given()
        {
            _emailAddress = "user@example.org";
        }

        protected override void When()
        {
            _email = SUT.Create(_emailAddress);
        }

        [Test]
        public void then_the_body_must_contain_the_reset_uri()
        {
            _email.Body.ShouldContain("/Password/Reset/");
        }

        [Test]
        public void then_the_email_must_be_for_the_user()
        {
            _email.To[0].Address.ShouldEqual(_emailAddress);
        }

        [Test]
        public void then_the_subject_must_be_the_expected()
        {
            _email.Subject.ShouldEqual("Your email reset link");
        }
    }
}
Fernando Correia
  • 20,349
  • 10
  • 79
  • 113
0

There is a very simple way to test the email contents in approvaltests (www.approvaltests.com or nuget). The Code is simply:

EmailApprovals.Verify(mail);

This will create the .eml file, and allow you to view the result in outlook. Furthermore, once you approve the result (rename the file to .approved), the test will pass without opening outlook. (It is a very similar process to what @whyleee mentioned, but doesn't use the configuration file.)

I have published a short video on the process on YouTube.

Cody Gray
  • 222,280
  • 47
  • 466
  • 543
llewellyn falco
  • 2,091
  • 13
  • 12
0

If you want to test it, I would recommend looking at mail4net: www.mail4net.com

It is a commercial product, but it allows you to send email using a fake SMTP service and then querying the fake.

// Create Mail4Net FakeClient.
var client = new Mail4Net.Client.FakeClient();

// Send email.
client.Send(from, to, subject, body);

And then, you can query the client:

// Count the number of emails sent.
var count = client.Count();

// Get first email.
var message = client[0];
Karl Gjertsen
  • 4,097
  • 8
  • 35
  • 61
0

My quick-and-dirty approach was very similar to the top answer by whylee except it does not require touching any config-files (because I might need different settings for different unit-tests)

inside your email-sending code

if (ConfigurationManager.AppSettings["smtpDir"] == null)
    smtp.DeliveryMethod = SmtpDeliveryMethod.Network;
else
{
    smtp.DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory;
    smtp.PickupDirectoryLocation = ConfigurationManager.AppSettings["smtpDir"];
}

In your unit-test

ConfigurationManager.AppSettings["smtpDirectory"] = dir;

//do some testing then change it back

ConfigurationManager.AppSettings["smtpDirectory"] = null;

PS. I know all the downsides, but just in case someone needs this...

Alex from Jitbit
  • 39,345
  • 13
  • 111
  • 109
0

I generally have an "OverrideEmailAddress" setting which I set to my own email and then any email tests you run won't go to your client or whoever they would originally go to. I have a helper method I send all emails through and this method will use the setting if it exists. Optionally you may add to the bottom of the email who the original recipient would have been.

If you need to then confirm that the email was received you would have to write some code to actually check the email address and then confirm the message is correct.

Not sure if this is what you mean or not.

Josh M.
  • 23,573
  • 23
  • 96
  • 160
0

I'd probably write a really thin abstraction layer on top of the .NET SmtpClient class. Then I could substitute with a mockup class for unit tests. Of course, you wouldn't be able to unit test the wrapper, but it should be practically trivial anyway and hardly ever change.

Vilx-
  • 97,629
  • 82
  • 259
  • 398
0

A quick and dirty solution I'm using for my integration tests is simply implement an email manager that stores the email in memory - which you can then access in your test assertions - instead of actually sending it. The maximum number of emails sent per test is currently two so I guess that's why it suffices in this case.

Of course you have to clean up this in-memory list of sent emails after every test.

Johan Maes
  • 539
  • 5
  • 11