2009-06-29 10 views
18

Quindi ho alcune cose SMTP nel mio codice e sto provando a testare il metodo.Come si crea un mockup di System.Net.Mail MailMessage?

Così ho provato a Mockup MailMessage ma sembra non funzionare mai. Credo che nessuno dei metodi sono virtuali o astratta, quindi non posso usare moq per deridere l'alto :(.

Quindi penso che devo farlo a mano ed è lì che mi sono bloccato.

* a mano intendo witting l'interfaccia e il wrapper ma lasciando moq ancora mockup l'interfaccia

Non so come scrivere la mia interfaccia e il mio wrapper (una classe che implementerà l'interfaccia che avrà il codice MailMessage effettivo così, quando il mio codice reale viene eseguito, fa esattamente ciò che deve fare)

Quindi prima non sono sicuro di come configurare la mia interfaccia. dai un'occhiata a uno dei campi che devo simulare.

MailMessage mail = new MailMessage(); 

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

quindi questa è la prima cosa che devo fingere.

così guardarla so che "A" è una struttura colpendo F12 su "A" mi porta a questa linea:

public MailAddressCollection To { get; } 

quindi è MailAddressCollection Proprietà. Ma alcuni come sono permesso di andare oltre e fare "Aggiungi".

Quindi ora la mia domanda è nella mia interfaccia che cosa faccio?

faccio una proprietà? Questa proprietà dovrebbe essere MailAddressCollection?

O dovrei avere un metodo come?

void MailAddressCollection To(string email); 

or 

void string To.Add(string email); 

Allora come sarebbe il mio involucro?

Quindi come potete vedere sono molto confuso. Poiché ce ne sono così tanti. Sto indovinando che sto solo simulando quelli che sto usando.

modificare il codice

Credo che in in un vero senso avrei solo per testare più le eccezioni ma voglio provare a fare in modo che tutto viene inviato allora otterrà alla risposta = successo.

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; 

     } 

Grazie

risposta

31

Perché prendere in giro MailMessage? SmtpClient riceve MailMessages e li invia; questa è la classe che vorrei avvolgere a scopo di test. Quindi, se si sta scrivendo un certo tipo di sistema che pone gli ordini, se si sta cercando di verificare che il vostro OrderService sempre e-mail quando viene fatto un ordine, si avrebbe una classe simile al seguente:

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 l'implementazione predefinita di IEmailService avvolgimento SmtpClient:

in questo modo, quando si va a scrivere il test di unità, di testare il comportamento del codice che utilizza le classi SmtpClient/EmailMessage, non il comportamento delle classi SmtpClient/EmailMessage Se stessi:

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

} 

Modifica: per affrontare i vostri commenti qui sotto, che ci si vuole due implementazioni separate di EmailService - solo si potrebbe usare SmtpClient, che usereste nel codice dell'applicazione:

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

} 

tuo deriso/falso servizio di posta elettronica (non hai bisogno di una struttura di derisione per questo, ma aiuta) non toccherebbe SmtpClient o SmtpSettings; registrerebbe solo il fatto che, ad un certo punto, è stata inviata una e-mail tramite SendEmail. È quindi possibile utilizzare questo per verificare se SendEmail è stato chiamato, e con quali parametri:

class MockEmailService : IEmailService { 
    private EmailMessage sentMessage;; 

    public SentMailMessage { get { return sentMessage; } } 

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

} 

La prova reale o meno l'e-mail è stato inviato al server SMTP e consegnati dovrebbe cadere al di fuori dei confini della vostra test unitario. È necessario sapere se questo funziona, ed è possibile impostare una seconda serie di test per testare specificamente questo (in genere chiamati test di integrazione), ma si tratta di test distinti separati dal codice che verifica il comportamento principale dell'applicazione.

+0

Hmm Penso di capire quello che dici ma fa doppio controllo. Stai dicendo che poiché MailMessage è tutto il codice che è stato scritto per me, non devo testare e poiché MailMessage non ha davvero nulla a che fare con i miei test anche che Mailmessage non causa realmente alcuna dipendenza. send() che crea la dipendenza giusta? Quindi, in realtà, devo solo falsificare la parte di invio per interrompere qualsiasi dipendenza, giusto? Il mio pensiero era, ma MailMessage era una dipendenza, ma ora che la guardo, posso vedere che non lo è. E 'corretto e cosa stai cercando di dire? – chobo2

+0

OH e un'altra cosa uso cose come ConfigurationManager.AppSettings ["SMTP"]; Quindi dovrei simulare ConfigurationManager per interrompere la dipendenza da un file AppConfig o dovrei semplicemente creare un file appConfig? – chobo2

+3

Giusto. Non si desidera testare MailMessage o SmtpClient; non è il tuo codice. Si desidera testare tutto ciò che USES MailMessage e SmtpClient si assicurano che: a) stiano creando un MailMessage con i campi corretti; e b) stanno effettivamente inviando l'e-mail. –

0

In .NET 4.0 è possibile utilizzare il "duck-digitando" di passare un'altra classe al posto di "System.Net.Mail". Ma nella versione precedente temo che non ci sia un altro modo, piuttosto che creare wrapper attorno a "System.Net.Mail" e alla classe di simulazione.

Se è un altro (migliore) modo, mi piacerebbe impararlo :).

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

Quindi come farei allora? Come nel mio involucro dovrei avere // proprietà di mail MyConstructor pubblico() { Mail = new MailMessage (0;} public void To.Add (string e-mail) { posta .To.Add (e-mail);} poi fare la stessa cosa per ogni singolo metodo che ho bisogno O posso farlo in qualche modo altro allora che – chobo2

+0

Sì, penso che ci sia nessun altro modo, come? Provalo. – TcKs

1

si finirà beffardo diverse classi qui (almeno due). Innanzitutto, è necessario un wrapper per la classe MailMessage.Creerei un'interfaccia per il wrapper, quindi il wrapper implementerà l'interfaccia. Nel tuo test, prenderai in giro l'interfaccia. In secondo luogo, fornirai un'implementazione fittizia come aspettativa all'interfaccia fittizia per MailAddressCollection. Poiché MailAddressCollection implementa Collection<MailAddress>, questo dovrebbe essere abbastanza semplice. Se il mocking di MailAddressCollection è problematico a causa di proprietà aggiuntive (non ho controllato), potresti avere il tuo wrapper restituirlo come IList<MailAddress>, che come interfaccia dovrebbe essere facile da prendere in giro.

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

Grazie non sono sicuro se ho bisogno di prenderlo in giro adesso. Questo è molto utile dal momento che quando faccio il mio mocking su alcune delle cose, allora posso seguire questo. Non sono sicuro di qualcosa però. Cosa succede se voglio usare To.Add(); Devo fare una proprietà con quella? Su un lato si sa come hanno fatto in modo che possano fare Mail.To.Add (...) come hanno ottenuto il metodo "aggiungi" da mostrare dopo "A" li ho persino visti ha fatto una proprietà dopo una proprietà. – chobo2

1

disclaimer: io lavoro a Typemock Invece di trovare qualche trucco è possibile utilizzare Typemock Isolator semplicemente falso che classe in una sola riga di codice:

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

quindi è possibile impostare il comportamento su di esso utilizzando Isolate.WhenCalled

+0

Ya Sento typemock è troppo male costa denaro :(Basta guardare la versione più economica è come $ 89. Non sono sicuro di quello che ottieni ma in questo momento posso ' Posso permettermi di comprare cose come questa, forse quando sono a scuola e ho un lavoro o contratto, ne varrà la pena acquistare – chobo2

+0

@ chobo2 devi ammettere che usando Isolator produce il codice più elegante e facile da usare che risolva questo problema, suppongo che la qualità abbia il suo prezzo Stai programmando con Blocco note perché Visual Studio costa molto (molto più di 90 $) –