#c# #unit-testing #azure-functions
#c# #модульное тестирование #azure-функции
Вопрос:
Меня попросили создать эту функцию Http azure ниже. Я пытаюсь написать макет модульного теста для вызова этого processEmail. Я предполагаю, что мой запрос будет точкой входа. Мой модульный тест должен иметь другое значение электронной почты для тестирования. Если бы я мог привести пример из моей функции ниже, это было бы здорово.
public async Task<IActionResult> ProcessEmail(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]
HttpRequest req, ILogger log) {
log.SmpLogInformation("Initializing SendGrid ProcessEmail");
var client =
new SendGridClient("key");
var requestBody = new StreamReader(req.Body).ReadToEnd();
var data = JsonConvert.DeserializeObject<EmailContent>(requestBody);
if(data == null) {
throw new ArgumentNullException("Can't proced further");
}
var message = new SendGridMessage();
message.AddTo(data.Email);
message.SetFrom(new EmailAddress("ff.com"));
message.AddContent("text/html", HttpUtility.HtmlDecode(data.Body));
message.SetSubject(data.Subject);
log.SmpLogDebug("Email sent through Send Grid");
await client.SendEmailAsync(message);
return (ActionResult)new OkObjectResult("Submited sucessfully");
}
public class EmailContent {
public string? Email { get; set; }
public string? Subject { get; set; }
public string Body { get; set; } = "";
}
}
Комментарии:
1. Как вы собираетесь издеваться над SendGridClient?
Ответ №1:
Во-первых, вам нужно издеваться над своим SendGridClient
, иначе вы будете делать фактические запросы во время своих модульных тестов, что было бы не очень хорошо.
Глядя на код SendGrid, SendGridClient
реализует интерфейс ISendGridClient
. Вместо того, чтобы запускать клиент с помощью var client = new SendGridClient("key");
, вы могли бы использовать внедрение зависимостей для внедрения экземпляра ISendGridClient
через конструктор:
public class ProcessEmail
{
private readonly ISendGridClient _client;
public ProcessEmail(ISendGridClient client)
{
_client = client;
}
Затем вы можете удалить эту строку:
var client = new SendGridClient("key");
А затем небольшое изменение в этой строке для использования введенного объекта:
await _client.SendEmailAsync(message);
Затем, когда вы приступите к написанию своего модульного теста, вы сможете создать макет для ISendGridClient
интерфейса, который позволяет настраивать и проверять поведение объектов. Вот пример использования Moq:
[TestClass]
public class ProcessEmailTests
{
private readonly Mock<ISendGridClient> _mockSendGridClient = new Mock<ISendGridClient>();
private readonly Mock<ILogger> _mockLogger = new Mock<ILogger>();
private ProcessEmail _processEmail;
private MemoryStream _memoryStream;
[TestInitialize]
public void Initialize()
{
// initialize the ProcessEmail class with a mock object
_processEmail = new ProcessEmail(_mockSendGridClient.Object);
}
[TestMethod]
public async Task GivenEmailContent_WhenProcessEmailRuns_ThenEmailSentViaSendgrid()
{
// arrange - set up the http request which triggers the run method
var expectedEmailContent = new ProcessEmail.EmailContent
{
Subject = "My unit test",
Body = "Woohoo it works",
Email = "unit@test.com"
};
var httpRequest = CreateMockRequest(expectedEmailContent);
// act - call the Run method of the ProcessEmail class
await _processEmail.Run(httpRequest, _mockLogger.Object);
// assert - verify that the message being sent into the client method has the expected values
_mockSendGridClient
.Verify(sg => sg.SendEmailAsync(It.Is<SendGridMessage>(sgm => sgm.Personalizations[0].Tos[0].Email == expectedEmailContent.Email), It.IsAny<CancellationToken>()), Times.Once);
}
private HttpRequest CreateMockRequest(object body = null, Dictionary<string, StringValues> headers = null, Dictionary<string, StringValues> queryStringParams = null, string contentType = null)
{
var mockRequest = new Mock<HttpRequest>();
if (body != null)
{
var json = JsonConvert.SerializeObject(body);
var byteArray = Encoding.ASCII.GetBytes(json);
_memoryStream = new MemoryStream(byteArray);
_memoryStream.Flush();
_memoryStream.Position = 0;
mockRequest.Setup(x => x.Body).Returns(_memoryStream);
}
if (headers != null)
{
mockRequest.Setup(i => i.Headers).Returns(new HeaderDictionary(headers));
}
if (queryStringParams != null)
{
mockRequest.Setup(i => i.Query).Returns(new QueryCollection(queryStringParams));
}
if (contentType != null)
{
mockRequest.Setup(i => i.ContentType).Returns(contentType);
}
mockRequest.Setup(i => i.HttpContext).Returns(new DefaultHttpContext());
return mockRequest.Object;
}
}
Полный код функции:
public class ProcessEmail
{
private readonly ISendGridClient _client;
public ProcessEmail(ISendGridClient client)
{
_client = client;
}
[FunctionName("ProcessEmail")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("Initializing SendGrid ProcessEmail");
var requestBody = new StreamReader(req.Body).ReadToEnd();
var data = JsonConvert.DeserializeObject<EmailContent>(requestBody);
if (data == null)
{
throw new ArgumentNullException("Can't proceed further");
}
var message = new SendGridMessage();
message.AddTo(data.Email);
message.SetFrom(new EmailAddress("ff.com"));
message.AddContent("text/html", HttpUtility.HtmlDecode(data.Body));
message.Subject = data.Subject;
log.LogDebug("Email sent through Send Grid");
await _client.SendEmailAsync(message);
return (ActionResult)new OkObjectResult("Submited sucessfully");
}
public class EmailContent
{
public string? Email { get; set; }
public string? Subject { get; set; }
public string Body { get; set; } = "";
}
}