2009-06-29 5 views
18

J'ai donc quelques trucs SMTP dans mon code et j'essaie de tester cette méthode.Comment faire une maquette de System.Net.Mail MailMessage?

Alors j'ai essayé de Mockup MailMessage mais ça ne semble jamais fonctionner. Je pense qu'aucune des méthodes sont virtuelles ou abstraites je ne peux pas utiliser moq pour se moquer it up :(.

Je suppose que je dois le faire à la main et c'est là où je suis coincé.

* à la main, je veux dire l'esprit de l'interface et le wrapper mais en laissant moq encore mockup l'interface

Je ne sais pas comment écrire mon interface et mon Wrapper (une classe qui implémenter l'interface qui aura le code MailMessage réelle donc quand mon vrai code fonctionne, il fait les choses qu'il doit faire)

Donc d'abord je ne sais pas comment configurer mon interface. Je jette un coup d'oeil à l'un des champs que j'ai à maquiller.

MailMessage mail = new MailMessage(); 

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

donc c'est la première chose que je dois faire semblant.

l'air si à ce que je sais que « A » est une propriété en appuyant sur F12 sur « A » il me faut à cette ligne:

public MailAddressCollection To { get; } 

Il est MailAddressCollection la propriété. Mais certains comment je suis autorisé à aller plus loin et faire "Ajouter".

Alors maintenant, ma question est dans mon interface qu'est-ce que je fais?

puis-je faire une propriété? Cette propriété doit-elle être MailAddressCollection?

Ou devrais-je avoir une méthode comme?

void MailAddressCollection To(string email); 

or 

void string To.Add(string email); 

Alors, à quoi ressemblerait mon emballage? Comme vous pouvez le voir, je suis très confus.

Comme il y en a tellement. Je devine que je viens de simuler ceux que j'utilise.

modifier le code

Je suppose que dans un vrai sens, je ne dois tester plus les exceptions, mais je veux tester pour vous assurer que si tout est envoyé alors il obtiendra la réponse = succès.

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; 

     } 

Merci

Répondre

31

Pourquoi la maquette MailMessage? Le SmtpClient reçoit MailMessages et les envoie; C'est la classe que je voudrais emballer à des fins de test. Donc, si vous écrivez un certain type de système qui passe les commandes, si vous essayez de vérifier que votre OrderService toujours des e-mails lorsqu'une commande est placée, vous auriez une classe semblable à ce qui suit:

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

} 

Avec l'implémentation par défaut de IEmailService SmtpClient d'emballage:

de cette façon, quand vous allez écrire votre test unitaire, vous testez le comportement du code qui utilise les classes SmtpClient/EmailMessage, pas le comportement des classes SmtpClient/EmailMessage eux-mêmes:

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

} 

Edit: pour répondre à vos commentaires ci-dessous, vous voulez deux implémentations distinctes de EmailService - seulement on utiliserait SmtpClient, que vous souhaitez utiliser dans votre code d'application:

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

} 

Votre moquée/truqué service de courrier électronique (vous n'avez pas besoin d'un cadre moqueur pour cela, mais ça aide) ne toucherait pas SmtpClient ou SmtpSettings; il enregistre seulement le fait que, à un moment donné, un e-mail lui a été transmis via SendEmail. Vous pouvez ensuite utiliser pour tester si oui ou non SendEmail a été appelé, et avec quels paramètres:

class MockEmailService : IEmailService { 
    private EmailMessage sentMessage;; 

    public SentMailMessage { get { return sentMessage; } } 

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

} 

Le test réel de savoir si oui ou non le courriel a été envoyé au serveur SMTP et livré devrait tomber en dehors des limites de votre tests unitaires. Vous devez savoir si cela fonctionne, et vous pouvez configurer un second ensemble de tests pour tester spécifiquement cela (généralement appelé tests d'intégration), mais ce sont des tests distincts distincts du code qui teste le comportement de base de votre application.

+0

Hmm Je crois que je comprends ce que tu dis mais que tu peux doubler. Dites-vous que MailMessage est tout le code qui a été écrit pour moi, je n'ai pas besoin de tester et puisque MailMessage n'a vraiment rien à voir avec mes tests, Mailmessage ne cause pas vraiment de dépendances, il est vraiment juste smpt. send() qui crée le droit de dépendance? Donc vraiment je dois juste simuler la partie d'envoi pour casser toutes les dépendances droit? Ma pensée était mais MailMessage était une dépendance mais maintenant que je le regarde je peux voir que ce n'est pas le cas. Est-ce correct et qu'est-ce que vous essayez de dire? – chobo2

+0

OH et encore une chose que j'utilise des choses comme ConfigurationManager.AppSettings ["SMTP"]; Alors, est-ce que je devrais maquiller ConfigurationManager pour ne pas dépendre d'un fichier AppConfig ou devrais-je simplement créer un fichier appConfig? – chobo2

+3

Droite. Vous ne voulez pas tester MailMessage ou SmtpClient; ce n'est pas ton code. Vous voulez tester unitairement tout ce qui utilise MailMessage et SmtpClient pour vous assurer que: a) ils créent un MailMessage avec les bons champs; et b) sont en train d'envoyer l'email. –

0

Dans .NET 4.0, vous pouvez utiliser le « canard tapant » pour passer à la place une autre classe de « System.Net.Mail ». Mais dans la version précédente, je crains qu'il n'y ait pas d'autre moyen que de créer un wrapper autour de "System.Net.Mail" et de la classe fictive.

Si c'est un autre (meilleur) moyen, j'aimerais l'apprendre :).

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

Alors, comment le ferais-je alors? Comme dans mon emballage dois-je avoir // propriété de mail MyConstructor() public { Mail = new MailMessage (0;} To.Add public void (email string) { Courrier .To.Add (e-mail);} puis faire la même chose pour chaque méthode simple j'ai besoin Ou puis-je faire d'autre alors que someway – chobo2

+0

Oui, je pense qu'il n'y a pas d'autre moyen, comment? Essaye-le. – TcKs

1

Vous finirez par se moquant de plusieurs classes différentes ici (au moins deux). Tout d'abord, vous avez besoin d'un wrapper autour de la classe MailMessage.Je voudrais créer une interface pour l'encapsuleur, puis que l'encapsuleur implémente l'interface. Dans votre test, vous allez simuler l'interface. Deuxièmement, vous allez fournir une implémentation fictive en guise d'attente à l'interface mockée pour MailAddressCollection. Puisque MailAddressCollection implémente Collection<MailAddress>, cela devrait être assez simple. Si le fait de se moquer de MailAddressCollection est problématique en raison de propriétés supplémentaires (je n'ai pas vérifié), vous pouvez demander à votre wrapper de le renvoyer sous forme de IList<MailAddress>, ce qui devrait être facile à simuler en tant qu'interface.

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

Merci, je ne suis pas sûr si j'ai vraiment besoin de se moquer maintenant. C'est très utile car quand je fais ma maquette de certaines choses, je peux suivre ça. Je ne suis pas sûr de quelque chose cependant. Que se passe-t-il si je veux utiliser le To.Add(); Dois-je faire une propriété avec celle-là? Sur une des notes de côté savez-vous comment ils l'ont fait pour qu'ils puissent faire Mail.To.Add (...) comme comment ont-ils obtenu la méthode "add" pour apparaître après "To" j'ai même vu qu'ils a fait une propriété après une propriété. – chobo2

1

Disclaimer: Je travaille à Typemock Au lieu de trouver un peu pirater, vous pouvez utiliser Typemock Isolateur simplement fausse cette classe dans une seule ligne de code:

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

Ensuite, vous pouvez définir le comportement à l'aide Isolate.WhenCalled

+0

Ya j'entends typemock est bien trop mauvais ça coûte de l'argent :(Juste regarder la version la moins chère est comme $ 89. Je ne sais pas ce que vous obtenez, mais en ce moment je peux ' Je ne peux pas me permettre d'acheter des choses comme ça peut-être quand je suis à l'école et que j'ai un emploi ou que je contracte alors ça vaudra la peine d'acheter – chobo2

+0

@ chobo2 vous devez admettre que Isolator produit le code le plus élégant et le plus facile à comprendre. Je suppose que la qualité a son prix Programmez-vous avec le Bloc-notes parce que Visual Studio coûte de l'argent (beaucoup plus que 90 $) –

Questions connexes