2009-06-29 18 views
18

Así que tengo algunas cosas SMTP en mi código y estoy tratando de probar ese método unitariamente.¿Cómo hago una maqueta de System.Net.Mail MailMessage?

Así que he estado tratando de maquetar MailMessage pero parece que nunca funciona. Creo que ninguno de los métodos son virtuales o abstractos, así que no puedo usar moq para burlarse hasta :(.

Así que supongo que tengo que hacerlo a mano y ahí es donde estoy atascado.

* a mano quiero decir con la interfaz y el envoltorio, pero dejando que moq siga simulando la interfaz.

No sé cómo escribir mi interfaz y mi Wrapper (una clase que implementará la interfaz que tendrá el código de MailMessage real) así que cuando mi código real se ejecuta realmente hace las cosas que necesita hacer).

Así que primero no estoy seguro de cómo configurar mi interfaz. Eche un vistazo a uno de los campos que tengo que maqueta.

MailMessage mail = new MailMessage(); 

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

así que esto es lo primero que tengo que fingir.

por lo mirarlo sé que "A" es una propiedad pulsando F12 sobre "A" que me lleva a la siguiente línea:

public MailAddressCollection To { get; } 

Por lo tanto, es MailAddressCollection propiedad. Pero de alguna forma puedo ir más allá y hacer "Agregar".

Así que ahora mi pregunta es en mi interfaz ¿qué hago?

hago una propiedad? ¿Debería esta propiedad ser MailAddressCollection?

¿O debería tener un método como?

void MailAddressCollection To(string email); 

or 

void string To.Add(string email); 

¿Cómo se vería mi envoltorio?

Así que como pueden ver, estoy muy confundido. Ya que hay tantos de ellos. Supongo que solo simulé los que estoy usando.

editar código

supongo que en cierto sentido en un sólo tendría que probar más las excepciones pero quiero poner a prueba para asegurarse de que si todo se envía entonces se llega a la respuesta = éxito.

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; 

     } 

Gracias

Respuesta

31

¿Por qué se burlan de MailMessage? El SmtpClient recibe MailMessages y los envía; esa es la clase que me gustaría envolver para fines de prueba. Por lo tanto, si usted está escribiendo algún tipo de sistema que realiza pedidos, si usted está tratando de probar que su OrderService siempre mensajes de correo electrónico cuando se hace un pedido, que tendría una clase similar a la siguiente:

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); 
    } 

} 

Con la implementación predeterminada de IEmailService que envuelve SmtpClient:

De esta manera, cuando vaya a escribir su prueba unitaria, pruebe el comportamiento del código que usa las clases SmtpClient/EmailMessage, no el comportamiento de las clases SmtpClient/EmailMessage ellos mismos:

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]")); 
    } 

} 

Editar: para hacer frente a sus comentarios a continuación, usted querrá dos implementaciones diferentes de EmailService - sólo uno sería utilizar SmtpClient, que tendrá que utilizar en el código de aplicación:

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); 
    } 

} 

Su burlado/fingido servicio de correo electrónico (no necesita un marco de burla para esto, pero ayuda) no tocaría SmtpClient o SmtpSettings; solo registraría el hecho de que, en algún momento, se le pasó un correo electrónico a través de SendEmail. A continuación, puede usar esto para probar si o no SendEmail fue llamado, y con qué parámetros:

class MockEmailService : IEmailService { 
    private EmailMessage sentMessage;; 

    public SentMailMessage { get { return sentMessage; } } 

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

} 

La prueba real de si el correo electrónico se envía al servidor SMTP y entregados debe caer fuera de los límites de su examen de la unidad. Necesita saber si esto funciona, y puede configurar un segundo conjunto de pruebas para probar específicamente esto (normalmente llamadas Pruebas de Integración), pero estas son pruebas distintas separadas del código que prueba el comportamiento principal de su aplicación.

+0

Hmm Creo que entiendo lo que dices, pero deja doble chekc. ¿Está diciendo que debido a que MailMessage es todo el código que se escribió para mí, no tengo que probarlo y dado que MailMessage no tiene realmente nada que ver con mis pruebas, además de que Mailmessage realmente no causa ninguna dependencia, realmente es solo snt. send() que crea la dependencia ¿verdad? Así que realmente solo tengo que falsificar la pieza de envío para romper cualquier dependencia ¿no? Mi pensamiento era que MailMessage era una dependencia, pero ahora que lo veo puedo ver que realmente no lo es. ¿Es correcto y qué intenta decir? – chobo2

+0

OH y una cosa más que uso cosas como ConfigurationManager.AppSettings ["SMTP"]; ¿Debo simular que ConfigurationManager rompa la dependencia de un archivo AppConfig o debería simplemente crear un archivo appConfig? – chobo2

+3

Derecha. No desea probar MailMessage o SmtpClient; no es tu código Desea probar de forma unitaria todo lo que usa USES MailMessage y SmtpClient para asegurarse de que: a) están creando un MailMessage con los campos correctos; yb) en realidad están enviando el correo electrónico. –

0

En .NET 4.0 que puede utilizar el "pato-escribir" para pasar a otra clase en lugar de "System.Net.Mail". Pero en una versión anterior, me temo que no hay otra forma, que crear un contenedor alrededor de "System.Net.Mail" y la clase de simulacro.

Si es otra (mejor) forma, me gustaría aprenderlo :).

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 */ 
} 
+0

Entonces, ¿cómo lo haría entonces? Al igual que en mi envoltorio debería tener // propiedad de correo Aquí MyConstructor pública() { correo = new MailMessage (0;} public void To.Add (e-mail cadena) { correo .To.Add (e-mail); } a continuación, hacerlo de la misma cosa para cada método único que necesito O puedo hacerlo de alguna manera lo demás entonces que – chobo2

+0

Sí, creo que no hay otra manera, cómo? Pruébalo. – TcKs

1

el resultado final será burlarse de varias clases diferentes aquí (al menos dos). Primero, necesitas un contenedor alrededor de la clase MailMessage.Crearía una interfaz para el wrapper, luego haría que el wrapper implementara la interfaz. En su prueba, simulará la interfaz. En segundo lugar, proporcionará una implementación simulada como una expectativa para la interfaz simulada para MailAddressCollection. Como MailAddressCollection implementa Collection<MailAddress>, esto debería ser bastante directo. Si burlarse de MailAddressCollection es problemático debido a propiedades adicionales (no revisé), podría hacer que su envoltorio lo devuelva como IList<MailAddress>, que como interfaz debería ser fácil de simular.

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)); 
    ... 
} 
+0

Gracias, no estoy seguro si necesito simularlo ahora. Esto es muy útil ya que cuando hago mi burla de algunas de las cosas, entonces puedo seguir esto. No estoy seguro de algo sin embargo. ¿Qué sucede si quiero usar To.Add(); ¿Debo hacer una propiedad con esa? En una nota lateral, ¿sabe cómo lo hicieron para que puedan hacer Mail.To.Add (...) cómo obtuvieron el método "agregar" para aparecer después de "Hasta" incluso vi que hizo una propiedad después de una propiedad. – chobo2

1

exención de responsabilidad: yo trabajo en Typemock En lugar de encontrar algún artilugio puede utilizar Typemock aislador simplemente falso que la clase en una sola línea de código:

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

entonces se puede establecer el comportamiento en él usando Aislar.Cuando Called

+0

Ya oigo typemock es tan malo que cuesta dinero :(Mirar la versión más barata es como $ 89. No estoy seguro de lo que obtienes pero en este momento puedo ' Si me permite comprar cosas como esta, tal vez cuando estoy en la escuela y tenga un trabajo o me contrate, valdrá la pena comprarlo. – chobo2

+0

@ chobo2 debe admitir que el uso de Isolator produce el código más elegante y fácil de usar que resuelve este problema. Supongo que la calidad tiene su precio. ¿Está programando con el Bloc de notas porque Visual Studio cuesta dinero (mucho más que 90 $)? –

Cuestiones relacionadas