Как мне сделать макет System.Net.Mail MailMessage? - PullRequest
19 голосов
/ 29 июня 2009

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

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

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

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

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

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

MailMessage mail = new MailMessage();

mail.To.Add("test@hotmail.com");

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

поэтому, глядя на это, я знаю, что «Кому» - это свойство, нажатие клавиши F12 над «Кому» приводит меня к этой строке:

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;

        } 

Спасибо

Ответы [ 4 ]

32 голосов
/ 29 июня 2009

Зачем издеваться над 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:

Таким образом, когда вы собираетесь написать свой модульный тест, вы тестируете поведение кода, использующего классы 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("test@hotmail.com"));
    }

}

Редактировать: чтобы ответить на ваши комментарии ниже, вам понадобятся две отдельные реализации 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-сервер и доставлено, должно выходить за пределы вашего модульного тестирования. Вам нужно знать, работает ли это, и вы можете настроить второй набор тестов, чтобы специально тестировать это (обычно это интеграционные тесты), но это отдельные тесты, отличные от кода, который тестирует поведение ядра вашего приложения.

1 голос
/ 01 июля 2009

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

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

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

1 голос
/ 29 июня 2009

В итоге вы будете издеваться над несколькими разными классами (как минимум, двумя). Во-первых, вам нужна оболочка для класса 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 = "test@example.com";

     message.Expect( m => m.To ).Return( to ).Repeat.Any();
     to.Expect( t => t.Add( expectedAddress ) );
     ...
}
0 голосов
/ 29 июня 2009

В .NET 4.0 вы можете использовать «утку», чтобы передать другой класс вместо «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 */
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...