Как сделать макет 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;
}
Спасибо
Ответы
Ответ 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 */
}