简体   繁体   中英

I created this Http Azure function with SendGrid and not sure how to write a unit test to call it with different email test case

I was asked to create this Http azure function bellow. I'm trying to write a mock unit test to call this processEmail. I'm guessing my req will be the entry point. My unit test should have different email value to test. If I could have an example from my function bellow that would be great.

   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; } = "";
    }
}

Firstly, you need to mock your SendGridClient otherwise you will be making actual requests during your unit tests which wouldn't be great.

Looking at the SendGrid code, SendGridClient implements an interface ISendGridClient . Instead of new'ing up the client using var client = new SendGridClient("key"); , you could use dependency injection to inject an instance of ISendGridClient via the constructor:

public class ProcessEmail
{
    private readonly ISendGridClient _client;

    public ProcessEmail(ISendGridClient client)
    {
        _client = client;
    }

You can then remove this line:

var client = new SendGridClient("key");

And then a slight change to this line to use the injected object:

await _client.SendEmailAsync(message);

Then when you come to write your unit test, you will be able to have a mock for the ISendGridClient interface which allows you to setup and verify behaviour of objects. Here's an example using 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;
    }
}

Full function code:

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; } = "";
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM