I've created some functions and have used this post https://www.rickvandenbosch.net/blog/azure-functions-binding-to-a-property/ to create them using custom properties
I set up the test project and files using the docs here https://learn.microsoft.com/en-us/azure/azure-functions/functions-test-a-function
My question is, how can functions that use property binding be unit tested? as all the examples I've looked at online use the test factory class to pass in a type of httpRequest.
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] GetStudyDetailsRequest req,
ILogger log)
{
try
{
var client = new ArtenaServiceClient(ArtenaServiceClient.EndpointConfiguration.BasicHttpBinding_IArtenaService);
var result = await client.GetStudyDetailsAsync(req.StudentID, req.Dev);
return CreateActionResult.Create(result);
}
catch (Exception e)
{
log.LogError(e, "", req);
return new OkObjectResult(new { e.Message, e.StackTrace });
}
}
I won't need to include Azure Function request property binding in my "unit" test scope firstly because that's not part of my code I am calling a unit here, secondly that's not my code at all :). That kind of testing might be relevant for end to end integration test where you would send http request to a running Function app. So, here in this case, I would just treat the function Run method as a regular C# method by passing required input directly from unit test.
Related
Ivé this code...
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "v1/Sala")]
HttpRequest req, [SignalR(HubName = "{query.HubName}")]
IAsyncCollector<SignalRMessage> signalRMessages) {
await signalRMessages.AddAsync(
new SignalRMessage {
Target = "newMessage",
Arguments = new[] { "Hello" }
});
}
And this code works good and I am happy. But I've a need, in partiular on my EventGridTrigger...
As you could noticed on the code above, the hubname is dinamic and an EventGridTrigger is a sort of special kind of endpoint (Your client app will not call and consume it...SignalR will instead).
But I am capable to identify the hubname on my EventGridTrigger...I can do this:
SignalRDataEvent data =
JsonConvert.DeserializeObject<SignalRDataEvent(eventGridEvent.Data.ToString());
string hubname = data.hubname;
But now...I need to send a signalR message using my variable hubname. Since I can't put [SignalR(HubName = "{query.HubName}")] IAsyncCollector signalRMessages) on my EventGridTrigger, I need to create the object SignalR, probably pass credentials, hubname, etc and then send a message. I can't find any sample for this - At least no samples that can work in serverless c# azure functions. Can someone help me wikth this ?
Try the following:
[SignalR(HubName = "{data.hubName}")]
I'm trying to figure out a way to design xUnit tests for my Azure Functions, which are based on this example.
As you can see, with .net 5 functions, you are expected to start func host start in your functions' project directory, then you can fire functions and debug.
Here's the program.cs of the functions project
var host = new HostBuilder()
.ConfigureAppConfiguration(c =>
{
c.AddCommandLine(args);
})
.ConfigureFunctionsWorker((c, b) =>
{
b.UseMiddleware<FunctionExceptionMiddleware>(); //an extension method of mine to register middlewares...
b.UseFunctionExecutionMiddleware();
})
.ConfigureServices(s =>
{
//add other services here,
CloudStorageAccount storageAccount = CloudStorageAccount.Parse("");
BlobServiceClient blobServiceClient = new BlobServiceClient("");
s.AddScoped(r => storageAccount);
s.AddScoped(r => blobServiceClient);
})
.Build();
As you can see, I register different types for DI to work properly, and I also register a middleware, to handle unhandled exceptions, so testing only the function's class Run method isn't optimal, because they rely on DI and also I need the middleware(s) to be tested as well.
Here an example function
[FunctionName("GetAsset")]
public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestData req, FunctionExecutionContext executionContext)
{
var response = new HttpResponseData(HttpStatusCode.OK);
var container = req.GetQueryStringParam("container"); //extension method to retrieve query parameters...
var blob = req.GetQueryStringParam("blob");
if (string.IsNullOrEmpty(container) || string.IsNullOrEmpty(blob))
{
executionContext.Logger.LogInformation($"GetAsset function - missing arguments in request string");
return new BadRequestObjectResult("");
}
return new OkObjectResult("Success");
}
As far as I can see, to get optimal tests, func.exe from the toolkit would need to be launched, then with an HttpClient I could make requests to http://localhost:7071/api/MyFunction.
I've thought of launching func.exe through Process.Start() before each of the tests, but it's kind of hackish as I don't really know when it is ready (func.exe compiles all assemblies before showing a "Worker process started and initialized" message) and tests could give false fails because of that.
Any ideas ?
After migrating my Azure Functions project to .NET 5, it has started wrapping my responses in a weird wrapper class.
For instance, consider the following endpoint:
public record Response(string SomeValue);
[Function("Get")]
public async Task<IActionResult> Get(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "get-something")]
HttpRequestData request)
{
return new OkObjectResult(new Response("hello world"));
}
Before, it would return:
{
"someValue": "hello world"
}
But now, it returns:
{
"Value": {
"SomeValue": "hello world"
},
"Formatters": [],
"ContentTypes": [],
"DeclaredType": null,
"StatusCode": 200
}
I get that it must be because it just tries to serialize the object result, but I can't find any documentation on how this is supposed to work in .NET 5.
My main function currently looks like this:
public static async Task Main()
{
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults(x =>
x.UseDefaultWorkerMiddleware())
.ConfigureAppConfiguration((_, builder) => builder
.AddJsonFile("local.settings.json", true)
.Build())
.ConfigureServices(ConfigureServices)
.Build();
await host.RunAsync();
}
My project is located here, in case someone are interested: https://github.com/sponsorkit/sponsorkit.io
Currently, my .NET 5 work is on a branch called feature/signup-flow.
Using IActionResult with Azure Functions in .NET 5?
You can't return IActionResult with Azure Functions in .NET 5. Or more generally, you can't return IActionResult with Azure Functions using the isolated process model. Quote from the docs:
For HTTP triggers, you must use HttpRequestData and HttpResponseData to access the request and response data. This is because you don't have access to the original HTTP request and response objects when running out-of-process.
Instead of IActionResult, you need to return HttpResponseData. Example code is here.
To return an object from .NET 5 Azure Functions, use the following code:
var response = req.CreateResponse(HttpStatusCode.OK);
await response.WriteAsJsonAsync(obj);
return response;
Was pleasantly surprised to find that returning Task from a "dotnet-isolated" function workins in Core 6. Saved me a few hours as I do not need to change to Task in a number of functions
After searching around and wanting to stay in the context of the IActionResult object, I ended up leveraging the ObjectResult object so that I can standardize all the service responses with the standardize JSON messages.
I am working on two functions in Azure Functions
Timer Function and Http Trigger Function
My timer functions runs every 1 hour and it executes the http function via an Http Client.
Now I do get an error Synchronous operations are disallowed
And I know how to solve this using the article on stack overflow
But I am curious as why am I getting this error?
Whats the cause of it?
The error doesn't occur when using Postman.
My Timer Code
Azure Functions Core Tools (3.0.2245 Commit hash: 1d094e2f3ef79b9a478a1621ea7ec3f93ac1910d)
Function Runtime Version: 3.0.13139.0
Host configuration file read:
{
"version": "2.0"
}
public static class DoSomeStuffTimer
{
[FunctionName("DoSomeStuffTimer")]
public static void Run([TimerTrigger("0 0 7 * * *")]TimerInfo myTimer, ILogger log)
{
try
{
log.LogInformation($"C# DoSomeStuffTimer executing at: {DateTime.Now}");
string url = Environment.GetEnvironmentVariable(EnvironmentKey.HostKey) + "/api/DoSomeStuff";
HttpClient client = new HttpClient();
client.PostAsJsonAsync(url, new DoSomeStuffRequest());
log.LogInformation($"C# DoSomeStuffTimer executed at: {DateTime.Now}");
}
catch (Exception e)
{
log.LogInformation(e.ToString());
}
}
}
My Http Code
public class DoSomeStuffFunction
{
[FunctionName("DoSomeStuffFunction")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "DoSomeStuff")]
HttpRequestMessage req,
ILogger log)
{
var response = new ContentResult {ContentType = "application/json", StatusCode = 200};
try
{
DoSomeStuffRequest
request = req.Content.ReadAsAsync<DoSomeStuffRequest>().Result;
}
catch (Exception e)
{
log.LogInformation(e.ToString());
}
}
}
Starting with ASP.NET Core 3.0 synchronous calls are disabled by default So any function running on 3.0 will encounter this when it tries to do synchronous calls
I looked into it and found the reason why it happens in your function is that ReadAsAsync<>() somewhere in it's operation does something synchronously. I am not sure exactly why it does this or why it doesn't break when you call the httptrigger directly. That'll require quite a bit more work to figure out.
To make your code work without FUNCTIONS_V2_COMPATIBILITY_MODE set to True you can use one of the other readers, for example ReadAsStreamAsync() instead.
Below you can find the method that works (I tested it locally). However, I would not recommend you call another function in your function app directly and instead follow the recommendations by Microsoft or create an abstraction that contains the logic that both functions can call on independently.
public class DoSomeStuffFunction
{
[FunctionName("DoSomeStuffFunction")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "DoSomeStuff")]
HttpRequestMessage req,
ILogger log)
{
var response = new ContentResult { ContentType = "application/json", StatusCode = 200 };
try
{
var request = await req.Content.ReadAsStreamAsync();
using (StreamReader rd = new StreamReader(request))
{
var result = JsonConvert.DeserializeObject<DoSomeStuffRequest>(await rd.ReadToEndAsync()).;
}
return new OkObjectResult(response);
}
catch (Exception e)
{
log.LogInformation(e.ToString());
return new BadRequestObjectResult("It went wrong");
}
}
}
The solution you mentioned is to set the variable FUNCTIONS_V2_COMPATIBILITY_MODE to true. We can see some information about this variable in this page.
So,did you do the operation to upgraded your function from v2 to v3 ? It may cause this issue.
Update:
I test it in my side on local visual studio. When I create the function app in visual studio, I choose azure function v3(.net core). And below is my code, it works fine without any error.
namespace FunctionApp8
{
public static class Function2
{
[FunctionName("Function2")]
public static void Run([TimerTrigger("0 */1 * * * *")]TimerInfo myTimer, ILogger log)
{
log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
string url = "http://localhost:7071/api/triggerFunction";
HttpClient client = new HttpClient();
client.PostAsJsonAsync(url, "");
log.LogInformation($"C# DoSomeStuffTimer executed at: {DateTime.Now}");
}
}
}
namespace FunctionApp8
{
public static class Function1
{
[FunctionName("triggerFunction")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
return (ActionResult)new OkObjectResult($"Hello");
}
}
}
I'm not sure if your function app is related to .net core 2.x. So could you please follow the steps recreate another function app(check if your visual studio has been updated to the lastest version and choose Azure Function v3 .NET core at the beginning) and test if it works fine.
How can I test in asp.net core 2.0 following method which exists in separate project than my test project? for example like this:
public partial class LoanRequestServiceController : BaseServiceController
{
public ServiceDTO<AP_CBO> AddCBO(AP_CBO cbo)
{
ServiceDTO<AP_CBO> dto = new ServiceDTO<AP_CBO>();
try
{
using (var scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.Snapshot }))
{
cbo.ID_CBO = 333;
dto.Data = cbo;
scope.Complete();
}
}
catch (Exception ex)
{
dto.Error = new ServiceError(ex);
Globals.Logger.Error(ex);
}
finally
{
//Globals.CastleComponentsContainer.Release(LoanRequestDAL);
}
return dto;
}
}
I tested some "light" methods such as if service method returns SucessCode and it works.
Here is my test class:
[Theory]
[InlineData("/Sample/AddCBO")]
public async Task Test_AddCBO(string url)
{
//Arrange
var client = _factory.CreateClient();
//Act
var response = await client.GetAsync(url);
//Assert
response.EnsureSuccessStatusCode();
//Compare two dto objects AP_CBO
//object expected = new AP_CBO { properties... }
// object responseObject = response.Content...
//Assert.Equal(expected, responseObject);
}
I don't know how to test an object with muliple properties.
Maybe I need to use Moq? Theoretically, this method would be go to the DAL (DatabaseAccess Layer) and return from database packed object and returns to the api, or in my case back into test.
First off, you have to decide which level of tests you want to write.
If you're writing a Unit test, you should mock any and all external integrations (in your case I can identify HTTP request -> Controller and Controller -> Database). This is the foundation of your functional testing. So if you're writing unit tests, yes, you should use a mocking framework such as NSubstitute or Moq (and only test your method's behavior by calling it).
The test sample you posted looks to me like an integration test since you're including the integration HTTP request -> Controller. In this case I would seed the database with data (if relevant) and actually call your API endpoint (as you're already doing).
To check the content (DTO) of the response in ASP.Net Core you have to do the following:
// ...
var response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
var content = await httpResponseMessage.Content.ReadAsStringAsync();
var serviceDto = JsonConvert.DeserializeObject<ServiceDTO<AP_CBO>>(content); // Only for Json
// Validate serviceDto
It is pretty long topic for detailed explanation here ; i think it will be better if you follow a sample and read the details.
I assume that you are going to write unit test; for unit test i can recommend this tutorial that may help you . check this please