From 1759e8a2d46ee361d67028e4ccb6109f11ebf439 Mon Sep 17 00:00:00 2001 From: "a.beging@eas-solutions.de" Date: Thu, 30 Apr 2026 11:14:15 +0200 Subject: [PATCH] Enhance MailService: refactor constructor to accept a custom SMTP client factory and add unit tests for SendEmailAsync method --- .../Service/MailService.cs | 7 +- FoodsharingSiegen.Tests/MailServiceTests.cs | 110 ++++++++++++++++++ 2 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 FoodsharingSiegen.Tests/MailServiceTests.cs diff --git a/FoodsharingSiegen.Server/Service/MailService.cs b/FoodsharingSiegen.Server/Service/MailService.cs index 9bf62e9..e5608c3 100644 --- a/FoodsharingSiegen.Server/Service/MailService.cs +++ b/FoodsharingSiegen.Server/Service/MailService.cs @@ -1,3 +1,4 @@ +using System; using System.Threading.Tasks; using FoodsharingSiegen.Contracts.Model; using MailKit.Net.Smtp; @@ -15,15 +16,17 @@ namespace FoodsharingSiegen.Server.Service { private readonly MailSettings _mailSettings; private readonly TermSettings _termSettings; + private readonly Func _smtpClientFactory; /// /// Initializes a new instance of the class. /// /// The configured application settings injected by DI, containing the . - public MailService(IOptions appSettings) + public MailService(IOptions appSettings, Func? smtpClientFactory = null) { _mailSettings = appSettings.Value.Mail; _termSettings = appSettings.Value.Terms; + _smtpClientFactory = smtpClientFactory ?? (() => new SmtpClient()); } /// @@ -40,7 +43,7 @@ namespace FoodsharingSiegen.Server.Service }; email.Body = textPart; - using var smtp = new SmtpClient(); + using var smtp = _smtpClientFactory(); var secureOptions = _mailSettings.UseSsl ? SecureSocketOptions.StartTls : SecureSocketOptions.Auto; await smtp.ConnectAsync(_mailSettings.Host, _mailSettings.Port, secureOptions); diff --git a/FoodsharingSiegen.Tests/MailServiceTests.cs b/FoodsharingSiegen.Tests/MailServiceTests.cs new file mode 100644 index 0000000..5e28c8d --- /dev/null +++ b/FoodsharingSiegen.Tests/MailServiceTests.cs @@ -0,0 +1,110 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using FoodsharingSiegen.Contracts.Model; +using FoodsharingSiegen.Server.Service; +using MailKit.Net.Smtp; +using MailKit.Security; +using Microsoft.Extensions.Options; +using MimeKit; +using Moq; +using Xunit; + +namespace FoodsharingSiegen.Tests +{ + public class MailServiceTests + { + private readonly Mock> _mockOptions; + private readonly AppSettings _appSettings; + private readonly Mock _mockSmtpClient; + + public MailServiceTests() + { + _appSettings = new AppSettings + { + Mail = new MailSettings + { + Host = "smtp.test.com", + Port = 587, + UseSsl = false, + Username = "user@test.com", + Password = "password123", + FromAddress = "no-reply@test.com" + }, + Terms = new TermSettings + { + Title = "Foodsharing Test" + } + }; + + _mockOptions = new Mock>(); + _mockOptions.Setup(o => o.Value).Returns(_appSettings); + + _mockSmtpClient = new Mock(); + } + + [Fact] + public async Task SendEmailAsync_ConnectsAuthenticatesAndSendsEmail() + { + // Arrange + var service = new MailService(_mockOptions.Object, () => _mockSmtpClient.Object); + var toEmail = "recipient@test.com"; + var subject = "Test Subject"; + var body = "

Test Body

"; + + // Act + await service.SendEmailAsync(toEmail, subject, body); + + // Assert + _mockSmtpClient.Verify( + x => x.ConnectAsync( + "smtp.test.com", + 587, + SecureSocketOptions.Auto, + It.IsAny()), + Times.Once); + + _mockSmtpClient.Verify( + x => x.AuthenticateAsync( + "user@test.com", + "password123", + It.IsAny()), + Times.Once); + + // Verify a MimeMessage is passed to SendAsync with correct attributes + _mockSmtpClient.Verify( + x => x.SendAsync( + It.Is(m => m.Subject == subject), + It.IsAny(), + It.IsAny()), + Times.Once); + + _mockSmtpClient.Verify( + x => x.DisconnectAsync( + true, + It.IsAny()), + Times.Once); + + _mockSmtpClient.Verify( + x => x.Dispose(), + Times.Once); + } + + [Fact] + public async Task SendEmailAsync_SkipsAuthentication_WhenUsernameIsBlank() + { + // Arrange + _appSettings.Mail.Username = ""; + _appSettings.Mail.Password = ""; + var service = new MailService(_mockOptions.Object, () => _mockSmtpClient.Object); + + // Act + await service.SendEmailAsync("recipient@test.com", "Subject", "Body"); + + // Assert + _mockSmtpClient.Verify( + x => x.AuthenticateAsync(It.IsAny(), It.IsAny(), It.IsAny()), + Times.Never); + } + } +} \ No newline at end of file