views:

147

answers:

6

I have a method like

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

public class ClassUnderTest : Base
{
    public override 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)

+7  A: 

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").

Jeff Sternal
Also note that if you don't have interface for Mailer and Mail, you can just write some thin wrappers around it..
Tigraine
I looked over setter injection and what you talked about with requiring it in the constructor and I ended up creating a property on the base class of type IMailer (set via a factory method an instance of ClassUnderTest is requested). In my test, before I call MethodUnderTest I set the Mailer object on Base to a Mock. It's not perfect, but it's close enough.
SnOrfus
A: 

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.

medopal
+2  A: 

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

Paul Williams
Why the downvote? We coded a really simple SMTP server component and used it to verify emails are sent. We then ran the listener on a background thread in our test application. All we had to do is point the SmtpClient to our server and fire away, and the server component verifies the email is sent correctly.
Paul Williams
+1  A: 

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.

FryGuy
+1, I should have mentioned this! Some people disparage this approach because it keeps `ClassUnderTest` tightly coupled to the concrete implementation (see [Flaw: Constructor does Real Work](http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/)), but it isn't a bad waypoint to hit on the way to dependency injection nirvana.
Jeff Sternal
A: 

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.

Jason Williams