c# - test - ¿Cómo hago una maqueta de System.Net.Mail MailMessage?
unit test rest api c# (4)
¿Por qué burlarse del MailMessage? El SmtpClient recibe mensajes de correo y los envía; esa es la clase que me gustaría envolver para propósitos de prueba. Por lo tanto, si está escribiendo algún tipo de sistema que realiza pedidos, si está tratando de comprobar que su OrderService siempre envía correos electrónicos cuando se realiza un pedido, tendrá 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 envolviendo SmtpClient:
De esta manera, cuando va a escribir su prueba de unidad, prueba el comportamiento del código que usa las clases SmtpClient / EmailMessage, no el comportamiento de las propias clases 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]"));
}
}
Edición: para abordar sus comentarios a continuación, desearía dos implementaciones separadas de EmailService; solo una usaría SmtpClient, que usaría en el código de su 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 servicio de correo electrónico simulado / falso (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. Luego puede usar esto para probar si se llamó o no a SendEmail, 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 envió o no al servidor SMTP y se entregó debe quedar fuera de los límites de la prueba de su unidad. Debe saber si esto funciona, y puede configurar un segundo conjunto de pruebas para probar específicamente esto (típicamente llamadas Pruebas de integración), pero estas son pruebas distintas separadas del código que prueba el comportamiento principal de su aplicación.
Así que tengo algo de SMTP en mi código y estoy tratando de probar ese método.
Así que estuve intentando hacer un Mockup MailMessage pero nunca parece funcionar. Creo que ninguno de los métodos es virtual o abstracto, por lo que no puedo usar moq para simularlo :(.
Así que supongo que tengo que hacerlo a mano y ahí es donde estoy atascado.
* a mano me refiero a encender la interfaz y la envoltura, pero dejar que moq todavía simule la interfaz.
No sé cómo escribir mi Interfaz y mi Envoltorio (una clase que implementará la interfaz que tendrá el código de MailMessage real, de modo que cuando mi código real se ejecute, realmente haga lo que debe hacer).
Así que primero no estoy seguro de cómo configurar mi interfaz. Echemos un vistazo a uno de los campos que tengo que maquetar.
MailMessage mail = new MailMessage();
mail.To.Add("[email protected]");
Así que esto es lo primero que tengo que fingir.
así que mirándolo, sé que "A" es una propiedad al presionar F12 sobre "A" y me lleva a esta línea:
public MailAddressCollection To { get; }
Así que es propiedad MailAddressCollection. Pero de alguna manera se me permite ir más allá y hacer "Agregar".
Así que ahora mi pregunta está 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);
Entonces, ¿cómo se vería mi envoltorio?
Así que como puedes ver estoy muy confundido. Ya que hay tantos de ellos. Estoy adivinando que solo imito a los que estoy usando.
editar código
Supongo que, en un sentido real, solo tendría que probar más las excepciones, pero quiero hacer una prueba para asegurarme de que todo se envíe y luego obtendrá una 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
En .NET 4.0, puede usar la "escritura de pato" para pasar otra clase en lugar de "System.Net.Mail". Pero en la versión anterior, me temo que no hay otra forma, que crear una envoltura alrededor de "System.Net.Mail" y la clase simulada.
Si es otra (mejor) manera, me gustaría aprenderlo :).
EDITAR:
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 */
}
Terminarás burlándote de varias clases diferentes aquí (al menos dos). Primero, necesitas un envoltorio alrededor de la clase MailMessage. Me gustaría crear una interfaz para el contenedor, y luego hacer que el contenedor implemente la interfaz. En tu prueba, te burlarás de la interfaz. En segundo lugar, proporcionará una implementación simulada como una expectativa para la interfaz simulada de MailAddressCollection. Dado que MailAddressCollection implementa la Collection<MailAddress>
, esto debería ser bastante sencillo. Si burlarse de MailAddressCollection es problemático debido a las propiedades adicionales (no verifiqué), puede hacer que su envoltorio lo devuelva como una IList<MailAddress>
, que como interfaz debería ser fácil de burlar.
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 ) );
...
}
descargo de responsabilidad: trabajo en Typemock En lugar de encontrar algún hack, puede usar el Aislador de Typemock para falsificar esa clase en una sola línea de código:
var fakeMailMessage = Isolate.Fake.Instance<MailMessage>();
Luego puede establecer el comportamiento en él utilizando Isolate.WhenCalled