Подтвердить что ты не робот

Как сделать макет System.Net.Mail MailMessage?

Итак, у меня есть некоторый SMTP-материал в моем коде, и я пытаюсь использовать unit test этот метод.

Итак, я пытаюсь Mockup MailMessage, но он никогда не работает. Я думаю, что ни один из методов не является виртуальным или абстрактным, поэтому я не могу использовать moq для его макета: (.

Так что, я думаю, я должен сделать это вручную, и там, где я застрял.

* вручную я подразумеваю witting интерфейс и оболочку, но пусть moq все еще макет интерфейса.

Я не знаю, как написать свой интерфейс и мой Wrapper (класс, который будет реализовывать интерфейс, который будет иметь фактический код MailMessage, поэтому, когда мой настоящий код запускает его, он действительно делает то, что ему нужно делать).

Итак, сначала я не уверен, как настроить свой интерфейс. Давайте взглянем на одно из полей, которые у меня есть для макета.

MailMessage mail = new MailMessage();

mail.To.Add("[email protected]");

так что это первое, что мне нужно подделать.

так что я смотрю на это. Я знаю, что "To" является свойством, нажав F12 на "To", он возвращает меня к этой строке:

public MailAddressCollection To { get; }

Итак, это свойство MailAddressCollection. Но некоторые, как мне разрешено идти дальше и делать "Добавить".

Итак, теперь мой вопрос в моем интерфейсе, что я делаю?

Я могу сделать свойство? Должно ли это свойство быть MailAddressCollection?

Или должен ли я иметь такой метод?

void MailAddressCollection To(string email);

or 

void string To.Add(string email);

Тогда как будет выглядеть моя обертка?

Итак, как вы можете видеть, я очень смущен. Поскольку их так много. Я предполагаю, что я просто макетирую те, которые я использую.

изменить код

Я предполагаю, что в истинном смысле мне нужно будет только проверять больше исключений, но я хочу проверить, чтобы убедиться, что все отправлено, тогда он получит ответ = успех.

string response = null;
            try
            {

                MembershipUser userName = Membership.GetUser(user);

                string newPassword = userName.ResetPassword(securityAnswer);

                MailMessage mail = new MailMessage();

                mail.To.Add(userName.Email);

                mail.From = new MailAddress(ConfigurationManager.AppSettings["FROMEMAIL"]);
                mail.Subject = "Password Reset";

                string body = userName + " Your Password has been reset. Your new temporary password is: " + newPassword;

                mail.Body = body;
                mail.IsBodyHtml = false;


                SmtpClient smtp = new SmtpClient();

                smtp.Host = ConfigurationManager.AppSettings["SMTP"];
                smtp.Credentials = new System.Net.NetworkCredential(ConfigurationManager.AppSettings["FROMEMAIL"], ConfigurationManager.AppSettings["FROMPWD"]);

                smtp.EnableSsl = true;

                smtp.Port = Convert.ToInt32(ConfigurationManager.AppSettings["FROMPORT"]);

                smtp.Send(mail);

                response = "Success";
            }
            catch (ArgumentNullException ex)
            {
                response = ex.Message;

            }
            catch (ArgumentException ex)
            {
                response = ex.Message;

            }
            catch (ConfigurationErrorsException ex)
            {
                response = ex.Message;
            }
            catch (ObjectDisposedException ex)
            {
                response = ex.Message;
            }
            catch (InvalidOperationException ex)
            {
                response = ex.Message;
            }
            catch (SmtpFailedRecipientException ex)
            {
                response = ex.Message;
            }
            catch (SmtpException ex)
            {
                response = ex.Message;
            }



            return response;

        } 

Спасибо

4b9b3361

Ответ 1

Зачем смеяться над MailMessage? SmtpClient получает MailMessages и отправляет их; что класс, который я хотел бы обернуть для целей тестирования. Итак, если вы пишете какую-то систему, в которой размещаются ордеры, если вы пытаетесь проверить, что ваш OrderService всегда будет отправлять электронные письма при размещении заказа, у вас будет класс, похожий на следующий:

class OrderService : IOrderSerivce 
{
    private IEmailService _mailer;
    public OrderService(IEmailService mailSvc) 
    {
        this. _mailer = mailSvc;
    }

    public void SubmitOrder(Order order) 
    {
        // other order-related code here

        System.Net.Mail.MailMessage confirmationEmail = ... // create the confirmation email
        _mailer.SendEmail(confirmationEmail);
    } 

}

При реализации IEmailService по умолчанию, использующей SmtpClient:

Таким образом, когда вы идете писать свой unit test, вы проверяете поведение кода, который использует классы SmtpClient/EmailMessage, а не поведение классов SmtpClient/EmailMessage:

public Class When_an_order_is_placed
{
    [Setup]
    public void TestSetup() {
        Order o = CreateTestOrder();
        mockedEmailService = CreateTestEmailService(); // this is what you want to mock
        IOrderService orderService = CreateTestOrderService(mockedEmailService);
        orderService.SubmitOrder(o);
    } 

    [Test]
    public void A_confirmation_email_should_be_sent() {
        Assert.IsTrue(mockedEmailService.SentMailMessage != null);
    }


    [Test]
    public void The_email_should_go_to_the_customer() {
        Assert.IsTrue(mockedEmailService.SentMailMessage.To.Contains("[email protected]"));
    }

}

Изменить: чтобы ответить на ваши комментарии ниже, вам понадобятся две отдельные реализации EmailService - только один будет использовать SmtpClient, который вы использовали бы в своем коде приложения:

class EmailService : IEmailService {
    private SmtpClient client;

    public EmailService() {
        client = new SmtpClient();
        object settings = ConfigurationManager.AppSettings["SMTP"];
        // assign settings to SmtpClient, and set any other behavior you 
        // from SmtpClient in your application, such as ssl, host, credentials, 
        // delivery method, etc
    }

    public void SendEmail(MailMessage message) {
        client.Send(message);
    }

}

Ваша фальшивая/фальшивая служба электронной почты (вам не нужна фальшивая инфраструктура для этого, но она помогает) не будет касаться SmtpClient или SmtpSettings; он только фиксирует тот факт, что в какой-то момент электронное письмо было отправлено через SendEmail. Затем вы можете использовать это, чтобы проверить, вызывался или нет SendEmail, и с какими параметрами:

class MockEmailService : IEmailService {
    private EmailMessage sentMessage;;

    public SentMailMessage { get { return sentMessage; } }

    public void SendEmail(MailMessage message) {
        sentMessage = message;
    }

}

Фактическое тестирование того, отправлялось ли электронное письмо на SMTP-сервер и доставлено, должно выходить за пределы вашего тестирования устройства. Вам нужно знать, работает ли это, и вы можете настроить второй набор тестов, чтобы специально протестировать это (обычно называемые интеграционными тестами), но это разные тесты, отличные от кода, который проверяет основное поведение вашего приложения.

Ответ 2

В итоге вы будете издеваться над несколькими разными классами (не менее двух). Во-первых, вам нужна оболочка вокруг класса MailMessage. Я бы создал интерфейс для обертки, а затем интерфейс оболочки реализовал оболочку. В вашем тесте вы будете макетировать интерфейс. Во-вторых, вы предоставите макетную реализацию в качестве ожидания для издевающегося интерфейса для MailAddressCollection. Поскольку MailAddressCollection реализует Collection<MailAddress>, это должно быть довольно прямолинейным. Если издевательство над MailAddressCollection является проблематичным из-за дополнительных свойств (я не проверял), вы могли бы заставить свою обертку вернуть ее как IList<MailAddress>, которая, как интерфейс, должна быть легко издеваться.

public interface IMailMessageWrapper
{
    MailAddressCollection To { get; }
}

public class MailMessageWrapper
{
    private MailMessage Message { get; set; }

    public MailMessageWrapper( MailMessage message )
    {
        this.Message = message;
    }

    public MailAddressCollection To
    {
        get { return this.Message.To; }
    }
}

// RhinoMock syntax, sorry -- but I don't use Moq
public void MessageToTest()
{
     var message = MockRepository.GenerateMock<IMailMessageWrapper>()
     var to = MockRepository.GenerateMock<MailAddressCollection>();

     var expectedAddress = "[email protected]";

     message.Expect( m => m.To ).Return( to ).Repeat.Any();
     to.Expect( t => t.Add( expectedAddress ) );
     ...
}

Ответ 3

Отказ от ответственности: я работаю на Typemock Вместо того, чтобы найти какой-то хак, вы можете использовать Typemock Isolator, чтобы просто подделать этот класс только в одной строке кода:

var fakeMailMessage = Isolate.Fake.Instance<MailMessage>();

Затем вы можете установить на нем поведение, используя Isolate.WhenCalled

Ответ 4

В .NET 4.0 вы можете использовать "duck-typing" для передачи другого класса вместо "System.Net.Mail". Но в более ранней версии, я боюсь, что нет другого способа, кроме создания обертки вокруг "System.Net.Mail" над макетным классом.

Если это другой (лучший) способ, я бы хотел его изучить:).

EDIT:

public interface IMailWrapper {
    /* members used from System.Net.Mail class */
}

public class MailWrapper {
    private System.Net.Mail original;
    public MailWrapper( System.Net.Mail original ) {
        this.original = original;
    }

    /* members used from System.Net.Mail class delegated to "original" */
}

public class MockMailWrapper {
   /* mocked members used from System.Net.Mail class */
}


void YourMethodUsingMail( IMailWrapper mail ) {
    /* do something */
}