开发者

Testing a method that sends e-mail without sending the mail

开发者 https://www.devze.com 2022-12-29 22:17 出处:网络
I have a method like public abstract class Base { public void MethodUnderTest(); } public class ClassUnderTest : Base

I have a method like

public abstract class Base
{
    public void MethodUnderTest();
}

public class ClassUnderTest : Base
{
    public over开发者_如何学Cride MethodUnderTest()
    {
        if(condition)
        {
            IMail mail = new Mail() { /* ... */ };
            IMailer mailer = new Mailer() { /* ... */ }

            mailer.Send(mail);
        }
        else
        {
            /* ... */
        }
    }
}

I have unit tests for this method, and the mail gets sent to myself, so it's not terrible (better than no test) but I'd prefer not to send the mail.

  • The problem I have is that I don't want test specific code in the class (ie. if (testMode) return; instead of sending the mail)
  • I don't know lots about DI, but I considered passing a mock IMailer into MethodUnderTest except that it overrides the base class, and no other class that derives from Base needs an IMailer object (I don't want to force implementers of Base to take an unnecessary IMailer in MethodUnderTest)

What else can I do?

(note: IMail and IMailer are part of an external library for sending e-mail. It's written in house, so I can modify it all I like if necessary, though I can't see a need to in this situation)


A standard approach using dependency injection would be to require an IMailer in ClassUnderTests's constructor. If you do that, you pass a mock mailer into your tests, and the base class doesn't need to know anything about mailing or mailers.

If that's undesirable for some reason (this is pretty rare, it's usually only relevant when you don't control the underlying classes), you can use setter injection ("property injection").


You (may) can use a pickup directory and set it to a directory that is not configured to send:

http://www.singular.co.nz/blog/archive/2007/11.aspx

http://www.singular.co.nz/blog/archive/2007/12/19/programmatically-setting-the-smtpclient-pickup-directory-location-at-runtime.aspx

http://forum.discountasp.net/showthread.php?t=4593


You could setup a really simple fake SMTP server that knows just enough to verify the client sends the email.


I would do something like this (considering you don't have a DI framework):

public class ClassUnderTest : Base
{
    private IMail mail;
    private IMailer mailer

    public ClassUnderTest()
    {
        mail = new Mail() { /* ... */ };
        mailer = new Mailer() { /* ... */ }
    }
    public ClassUnderTest(IMail mail, IMailer mailer)
    {
        this.mail = mail;
        this.mailer = mailer;
    }

    public override MethodUnderTest()
    {
        if(condition)
        {
            mailer.Send(mail);
        }
        else
        {
            /* ... */
        }
    }
}

Then in your test, just call the second constructor, rather than the default one.


My answer in general (I don't know C#)

I once needed to make something like this in Java. I made the test to check if the port of sending opens then that means it's almost done. I force stopping of the process.


You can intercept the call to send the mail at many levels, but you still have to intercept it somewhere, or put up with receiving the emails.

This could be:

  • Put up with the test emails.

  • Add an email filter that deletes the emails or stores them in a different folder so they don't bother you.

  • Send your test emails to a different email address. Set up your server to just delete these emails when received (or keep them for 14 days like spam, so you can review them if you wish).

  • Replace your email server with a fake local one

  • Replace the IMailer's imailer.dll or IMailer implementation class with an entire mocked equivalent, or with one that siply doesn't implement the full SendMail behaviour.

  • Add a base class method to SendMail(), and disable its behaviour in the base class when running a unit test.

  • Add a virtual SendMail() method in ClassUnderTest and then create a derived class to be unit tested that simply overrides SendMail() with a mocked implementation.

  • Pass in the interface/object that it uses to call SendMail

  • Pass in a flag to indicate if it is being tested.

  • Redesign the class entirely so that it has a SendReport() and the report might be sent by email, TCP/IP, log file, etc.

  • etc.

The best approaches don't require changes to the actual method being tested, of course. Pick whichever one works best for your situation, and the other unit tests you need to add for this class.

0

精彩评论

暂无评论...
验证码 换一张
取 消