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 - c#

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

Related

Write integration test for grpc service

I use this repository to write an ASP.NET Core gRPC integration test. But if I have a service method that this call another gRPC service, I get an error that means the second service is not available.
My method code is something like this:
public async Task<GetPersonReply> GetPersonInfoAsync(GetPersonRequest request, CallContext context = default)
{
HttpContext httpContext = context.ServerCallContext.GetHttpContext();
LanguageExt.Option<string> userDisplayName = httpContext.User.Identity.Name;
GrpcChannel channel = Toolkit.ChannelFactory.CreateChannelWithCredentials("https://localhost:5201");
IAddressService client = channel.CreateGrpcService<IAddressService>();
GetAddressReply serviceReply = await client.GetAddressAsync(
new GetAddressRequest { Street = "test setree", ZipCode = "428" });
return new GetPersonReply
{
DisplayName = userDisplayName.Some(x => x).None(string.Empty),
Address = serviceReply.Address
};
}
My fixture class:
namespace IntegrationTests.Fixture
{
public sealed class TestServerFixture : IDisposable
{
private readonly WebApplicationFactory<Startup> _serverFactory;
private readonly WebApplicationFactory<SecondServer.Startup> _secondServerFactory;
public TestServerFixture()
{
_serverFactory = new WebApplicationFactory<Startup>();
_secondServerFactory = new WebApplicationFactory<SecondServer.Startup>();
HttpClient serverClient = _serverFactory.CreateDefaultClient(new ResponseVersionHandler());
HttpClient secondServerClient = _secondServerFactory.CreateDefaultClient(new ResponseVersionHandler());
ServerGrpcChannel = Toolkit.ChannelFactory.CreateChannelWithCredentials(
Contracts.GrpcUrlConstants.SERVER_GRPC_URL,
serverClient);
SecondServerGrpcChannel = Toolkit.ChannelFactory.CreateChannelWithCredentials(
Contracts.GrpcUrlConstants.SECOND_SERVER_GRPC_URL,
serverTestClient);
}
public GrpcChannel ServerGrpcChannel { get; }
public GrpcChannel SecondServerGrpcChannel { get; }
public void Dispose()
{
_serverFactory.Dispose();
_serverTestFactory.Dispose();
}
private class ResponseVersionHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
response.Version = request.Version;
return response;
}
}
}
}
And my test code :
namespace IntegrationTests
{
[Collection(TestCollections.ApiIntegration)]
public class PersonServiceAcceptanceTests
{
public PersonServiceAcceptanceTests(TestServerFixture testServerFixture)
{
GrpcChannel serverChannel = testServerFixture.ServerGrpcChannel;
GrpcChannel secondServerChannel = testServerFixture.TestServerGrpcChannel;
_clientPersonService = serverChannel.CreateGrpcService<IPersonService>();
_clientAddressService = testServerChannel.CreateGrpcService<IAddressService>();
}
private readonly IPersonService _clientPersonService;
private readonly IAddressService _clientAddressService;
[Theory]
[InlineData("test1", "987")]
[InlineData("test2", "123")]
public async Task GetAddressService_ShouludCall_Success(string street, string zipCode) --> this test pass successfully
{
GetAddressRequest request = new GetAddressRequest { Street = street, ZipCode = zipCode };
GetAddressReply result = await _clientAddressService.GetAddressAsync(request, CallContext.Default);
result.Should().NotBeNull();
result.Address.Should().NotBeNullOrWhiteSpace();
result.Address.Should().Contain(street);
result.Address.Should().Contain(zipCode);
}
[Fact]
public async Task GetPersonInfo_Should_Success() //My Issue -> this test has error and not pass
{
GetPersonRequest request = new GetPersonRequest { PersonId = "101" };
GetPersonReply result = await _clientPersonService.GetPersonInfoAsync(request, CallContext.Default);
result.Should().NotBeNull();
}
}
}
Is there anyone tell me how can I write an integration test containing two separate services that one call in another?

How to pass a class instance as parameter

I have created a class and I am adding to this class values (when I debug they are there) however once the method below is called the client is empty. Can you please advise what am I doing wrong? I understand that the class is a reference type, however how can I make use of this? I am just a bit confused.
According to this I don't see where I am going wrong https://softwareengineering.stackexchange.com/questions/264525/best-oop-practice-in-c-passing-the-object-as-parameter-vs-creating-a-new-insta
Please explain
public class SesEmailSender : IEmailSender
{
ClientAmazon clientAmazon;
public SesEmailSender()
{
var clientAmazon = new ClientAmazon();
}
public async Task<SendResponse> SendAsync(EmailMessage email)
{
var response = new SendResponse();
var mailMessage = CreateEmailMessage(email);
using (var client = SesSenderExtension.GetClient(clientAmazon))
{
await client.SendEmailAsync(mailMessage).ConfigureAwait(false);
}
return response;
}
}
public static class SesSenderExtension
{
public static ClientAmazon JsonParse()
{
var myJsonString = File.ReadAllText("msgSettings.json");
var myJObject = JObject.Parse(myJsonString);
var client = new ClientAmazon()
{
AccessKeyID = myJObject.SelectToken("AccessKeyId").Value<string>(),
SecretAccessKey = myJObject.SelectToken("SecretAccessKey").Value<string>()
};
return client;
}
public static AmazonSimpleEmailServiceClient GetClient(ClientAmazon client)
{
return new AmazonSimpleEmailServiceClient(client.AccessKeyID, client.SecretAccessKey, Amazon.RegionEndpoint.EUCentral1);
}
}
callin in the console
static void Main(string[] args)
{
GetAuthentificationDetails();
SesSenderExtension.JsonParse();
var sesEmailSender = new SesEmailSender();
var email = new EmailMessage()
{
Sender = "ccc#gmail.com",
Reciever = "xxx#gmail.com",
Subject = "test",
Body = "test",
};
var response = sesEmailSender.Send(email);
}
You need to change your constructor to this. Right now you are assigning to a local variable instead of your class property.
So change the constructor from
public SesEmailSender()
{
var clientAmazon = new ClientAmazon();
}
To this:
public class SesEmailSender : IEmailSender
{
ClientAmazon clientAmazon;
public SesEmailSender()
{
// remove var here
clientAmazon = new ClientAmazon();
}
public async Task<SendResponse> SendAsync(EmailMessage email)
{
var response = new SendResponse();
var mailMessage = CreateEmailMessage(email);
using (var client = SesSenderExtension.GetClient(clientAmazon))
{
await client.SendEmailAsync(mailMessage).ConfigureAwait(false);
}
return response;
}
}
Update based on your comments:
In your Program.cs you are doing this:
SesSenderExtension.JsonParse();
This will set the credentials on a different ClientAmazon.
Then later you create a NEW ClientAmazon in your constructor. That instance will not have any credentials set on it.
To get around this you could do something like this:
Get the configured client from SesSenderExtension.JsonParse()
Refactor SesEmailSender to take a ClientAmazon in it's constructor
Pass the ClientAmazon to the SesEmailSender constructor.
Use the ClientAmazon that you passed into the constructor.
Profit
static void Main(string[] args)
{
GetAuthentificationDetails();
var client = SesSenderExtension.JsonParse();
var sesEmailSender = new SesEmailSender(client);
var email = new EmailMessage()
{
Sender = "ccc#gmail.com",
Reciever = "xxx#gmail.com",
Subject = "test",
Body = "test",
};
var response = sesEmailSender.Send(email);
}
public class SesEmailSender : IEmailSender
{
ClientAmazon _clientAmazon;
public SesEmailSender(ClientAmazon clientAmazon)
{
_clientAmazon = clientAmazon;
}
public async Task<SendResponse> SendAsync(EmailMessage email)
{
var response = new SendResponse();
var mailMessage = CreateEmailMessage(email);
using (var client = SesSenderExtension.GetClient(_clientAmazon))
{
await client.SendEmailAsync(mailMessage).ConfigureAwait(false);
}
return response;
}
}

Mocking RestSharp response

I am trying to write an unit test at the moment and i am having difficult with the mocking the RestSharp. The test i am trying to write is for the GetAll method.
This is the code that i am trying to test.
public class Client: IClient
{
public IRestClient RestClient { get; set; }
public IOptions<ClientSettings>Settings { get; set; }
public Client(IOptions<ClientSettings>options)
{
Settings = options;
RestClient = new RestClient(options.Value.BaseUrl);
}
public async Task<List<EventDTO>> GetAll()
{
var allEvents = await RetrieveAllEvents();
var data = TransformData(allEvents);
return data;
}
private static List<EventDTO> TransformData(IEnumerable<Event> allEvents)
{
var data = allEvents.SelectMany(con =>
con.Geometries.Select(geo =>
new EventDTO
{
Title = con.Title,
Id = con.Sources.FirstOrDefault()?.Id,
CategoriesTitle = con.Categories.FirstOrDefault()?.Title,
Closed = con.Closed,
DateTime = geo.Date
})
).ToList();
return data;
}
private async Task<IEnumerable<Event>> RetrieveAllEvents()
{
var openEvents = await RetrieveEvent(Settings.Value.GetAllOpen);
var closedEvents = await RetrieveEvent(Settings.Value.GetAllClosed);
var allEvents = openEvents.Events.Concat(closedEvents.Events);
return allEvents;
}
private async Task<RootObject> RetrieveEvent(string request)
{
var responseData = new RestRequest(request, Method.GET);
var content = await RestClient.GetAsync<RootObject>(responseData);
return content;
}
}
When the code gets to this line, it just stops working. I tried putting in a try and catch around it see what the error is but it just blows the stack.
var data = await RestClient.GetAsync<RootObject>(responseData);
I saw an example online and i tried mocking the RestSharp
restClient.Setup(c => c.ExecuteAsync<EventDTO>(
Moq.It.IsAny<IRestRequest>(),
Moq.It.IsAny<Action<IRestResponse<EventDTO>, RestRequestAsyncHandle>>()))
.Callback<IRestRequest, Action<IRestResponse<EventDTO>, RestRequestAsyncHandle>>((request, callback) =>
{
var responseMock = new Mock<IRestResponse<EventDTO>>();
responseMock.Setup(r => r.Data).Returns(new EventDTO() { });
callback(responseMock.Object, null);
});

Mock returns null value when ReturnResult is a custom object but works as expected when it is a primitive type bool

I am using Moq in .net core(1.1) and having a bit of torrid time understanding this behavior as all the examples on interweb points to the fact the this should work with no issues.
I have already tried with:
Returns(Task.FromResult(...)
Returns(Task.FromResult(...)
ReturnsAsync(...)
Mocking a IHttpClient interface to wrap PostAsync, PutAsync and GetAsync. All of these return an ApiResponse object.
var mockClient = new Mock<IHttpClient>();
Does not work:
mockClient.Setup(x => x.PostAsync(url, JsonConvert.SerializeObject(body), null))
.Returns(Task.FromResult(new ApiResponse()));
PostSync definition:
public async Task<ApiResponse> PostAsync(string url, string body, string authToken = null)
Does work:
mockClient.Setup(x => x.PostAsync(url, JsonConvert.SerializeObject(body), null))
.Returns(Task.FromResult(bool));
PostSync definition:
public async Task<bool> PostAsync(string url, string body, string authToken = null)
Usage:
var api = new ApiService(mockClient.Object);
var response = api.LoginAsync(body.Username, body.Password);
UPDATE
[Fact]
public async void TestLogin()
{
var mockClient = new Mock<IHttpClient>();
mockClient.Setup(x => x.PostAsync(url, JsonConvert.SerializeObject(body), null)).Returns(Task.FromResult(new ApiResponse()));
var api = new ApiService(mockClient.Object);
var response = await api.LoginAsync(body.Username, body.Password);
Assert.IsTrue(response);
}
Return Type:
public class ApiResponse
{
public string Content { get; set; }
public HttpStatusCode StatusCode { get; set; }
public string Reason { get; set; }
}
LoginAsync:
public async Task<bool> LoginAsync(string user, string password)
{
var body = new { Username = user, Password = password };
try
{
var response = await _http.PostAsync(_login_url, JsonConvert.SerializeObject(body), null);
return response .State == 1;
}
catch (Exception ex)
{
Logger.Error(ex);
return false;
}
}
PostAsync:
public async Task<object> PostAsync(string url, string body, string authToken = null)
{
var client = new HttpClient();
var content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.PostAsync(new Uri(url), content);
var resp = await response.Result.Content.ReadAsStringAsync();
return new ApiResponse
{
Content = resp,
StatusCode = response.Result.StatusCode,
Reason = response.Result.ReasonPhrase
};
}
Assuming a simple method under test like this based on minimal example provided above.
public class ApiService {
private IHttpClient _http;
private string _login_url;
public ApiService(IHttpClient httpClient) {
this._http = httpClient;
}
public async Task<bool> LoginAsync(string user, string password) {
var body = new { Username = user, Password = password };
try {
var response = await _http.PostAsync(_login_url, JsonConvert.SerializeObject(body), null);
return response.StatusCode == HttpStatusCode.OK;
} catch (Exception ex) {
//Logger.Error(ex);
return false;
}
}
}
The following test works when configured correctly
[Fact]
public async Task Login_Should_Return_True() { //<-- note the Task and not void
//Arrange
var mockClient = new Mock<IHttpClient>();
mockClient
.Setup(x => x.PostAsync(It.IsAny<string>(), It.IsAny<string>(), null))
.ReturnsAsync(new ApiResponse() { StatusCode = HttpStatusCode.OK });
var api = new ApiService(mockClient.Object);
//Act
var response = await api.LoginAsync("", "");
//Assert
Assert.IsTrue(response);
}
The above is just for demonstrative purposes only to show that it can work provided the test is configured properly and exercised based on the expected behavior.
Take some time and review the Moq quick start to get a better understanding of how to use the framework.

How to mock RestSharp portable library in Unit Test

I would like to mockup the RestClient class for test purposes
public class DataServices : IDataServices
{
private readonly IRestClient _restClient;
public DataServices(IRestClient restClient)
{
_restClient = restClient;
}
public async Task<User> GetUserByUserName(string userName)
{
User user = null;
// create a new request
var restRequest = new RestRequest("User", Method.GET);
// create REST parameters
restRequest.AddParameter("userName", userName, ParameterType.QueryString);
// execute the REST request
var restResponse = await _restClient.Execute<User>(restRequest);
if (restResponse.StatusCode.Equals(HttpStatusCode.OK))
{
user = restResponse.Data;
}
return user;
}
}
My test class :
[TestClass]
public class DataServicesTest
{
public static IRestClient MockRestClient<T>(HttpStatusCode httpStatusCode, string json)
{
var mockIRestClient = new Mock<IRestClient>();
mockIRestClient.Setup(x => x.Execute<T>(It.IsAny<IRestRequest>()))
.Returns(new RestResponse<T>
{
Data = JsonConvert.DeserializeObject<T>(json),
StatusCode = httpStatusCode
});
return mockIRestClient.Object;
}
[TestMethod]
public async void GetUserByUserName()
{
var dataServices = new DataServices(MockRestClient<User>(HttpStatusCode.OK, "my json code"));
var user = await dataServices.GetUserByUserName("User1");
Assert.AreEqual("User1", user.Username);
}
}
But I can't instantiate the RestResponse object, I've the following error:
.Returns(new RestResponse<T>
{
Data = JsonConvert.DeserializeObject<T>(json),
StatusCode = httpStatusCode
});
Cannot access protected internal constructor 'RestResponse' here.
How can I workaround this ? I'm using the FubarCoder.RestSharp nuget package on a Xamarin portable Library.
Mock IRestResponse<T> and return that
public static IRestClient MockRestClient<T>(HttpStatusCode httpStatusCode, string json)
where T : new() {
var data = JsonConvert.DeserializeObject<T>(json)
var response = new Mock<IRestResponse<T>>();
response.Setup(_ => _.StatusCode).Returns(httpStatusCode);
response.Setup(_ => _.Data).Returns(data);
var mockIRestClient = new Mock<IRestClient>();
mockIRestClient
.Setup(x => x.Execute<T>(It.IsAny<IRestRequest>()))
.ReturnsAsync(response.Object);
return mockIRestClient.Object;
}
The test should also be updated to be async as well
[TestMethod]
public async Task GetUserByUserName() {
//Arrange
var client = MockRestClient<User>(HttpStatusCode.OK, "my json code");
var dataServices = new DataServices(client);
//Act
var user = await dataServices.GetUserByUserName("User1");
//Assert
Assert.AreEqual("User1", user.Username);
}
I didn't find any great answers so I ended up writing a helper library. I published it to NuGet - MoqRestSharp.Helpers. This project is aimed to help unit test RestSharp as it extends Mock so this helped me test my RestSharp requests and response error handling.
It uses Moq
NuGet Link
Repository Link - Examples are in the project too
Feedback is always welcome!
Complete solution
using Moq;
using Newtonsoft.Json;
using NUnit.Framework;
using RestSharp;
using System.Net;
namespace RestMockTest
{
public class Tests
{
[Test]
public void Test1()
{
var client = MockRestClient<User>(HttpStatusCode.OK, "{\"Name\":\"User1\"}");
var restRequest = new RestRequest("api/item/", Method.POST);
var restResponse = client.Execute<User>(restRequest);
var user = restResponse.Data;
Assert.AreEqual("User1", user.Name);
}
public static IRestClient MockRestClient<T>(HttpStatusCode httpStatusCode, string json)
where T : new()
{
var data = JsonConvert.DeserializeObject<T>(json);
var response = new Mock<IRestResponse<T>>();
response.Setup(_ => _.StatusCode).Returns(httpStatusCode);
response.Setup(_ => _.Data).Returns(data);
var mockIRestClient = new Mock<IRestClient>();
mockIRestClient
.Setup(x => x.Execute<T>(It.IsAny<IRestRequest>()))
.Returns(response.Object);
return mockIRestClient.Object;
}
}
public class User
{
public string Name { get; set; }
}
}

Categories