Testing timer triggers in Azure Functions
One of the great things about Azure Functions is being able to setup a function that can be scheduled to execute periodically without having to setup something to manage the scheduling. There are products like HangFire and Quarz.NET that help with this, but these still require you to manage the backing persistent and all the plumbing before you get start. These products are great and also give you an insight of what has been running but Azure Functions highly accelerates getting your app to production.
The Azure Timer Trigger
The Azure timer trigger is pretty simple, say that we want to call a HTTP endpoint with some data every morning at 8:00am, we would create some code like below.
public static class SendDataFunction
{
private static HttpClient _client = new HttpClient();
[FunctionName("SendDataFunction")]
public static async Task Run([TimerTrigger("0 0 8 * * *")]TimerInfo myTimer, ILogger log)
{
await _client.PostAsync("http://requestbin.net/r/1l5v3y41", new StringContent("Hello"));
}
}
The 0 0 8 * * *
string argument of TimerTrigger
is a cron expression that tells the runtime to call this function at 8:00am every morning.
Testing the Timer Trigger
As you can imagine waiting until 8:00am every morning would mean you could only test this function once a day, this isn’t ideal so there is a better way.
What we can do is make a http request to the admin endpoint and pass in the function in the uri and just a null input body.
Below is an example http request using VS Code and the REST Client extension.
POST http://localhost:7071/admin/functions/SendDataFunction
Content-Type: application/json
{
"input": null
}
We can also pull out the function name and the host and port in to variables.
@function = SendDataFunction
@host = localhost:7071
POST http://{{host}}/admin/functions/{{function}}
Content-Type: application/json
{
"input": null
}
Now if we execute the request we will get the following output in our Azure Functions console.
[20/02/2020 22:10:39] Host lock lease acquired by instance ID '0000000000000000000000008544E626'.
[20/02/2020 22:10:39] Executing HTTP request: {
[20/02/2020 22:10:39] "requestId": "9432ad7c-d6b1-48de-8524-cb7586bcd32a",
[20/02/2020 22:10:39] "method": "POST",
[20/02/2020 22:10:39] "uri": "/admin/functions/SendDataFunction"
[20/02/2020 22:10:39] }
[20/02/2020 22:10:40] Executed HTTP request: {
[20/02/2020 22:10:40] "requestId": "9432ad7c-d6b1-48de-8524-cb7586bcd32a",
[20/02/2020 22:10:40] "method": "POST",
[20/02/2020 22:10:40] "uri": "/admin/functions/SendDataFunction",
[20/02/2020 22:10:40] "identities": [
[20/02/2020 22:10:40] {
[20/02/2020 22:10:40] "type": "WebJobsAuthLevel",
[20/02/2020 22:10:40] "level": "Admin"
[20/02/2020 22:10:40] },
[20/02/2020 22:10:40] {
[20/02/2020 22:10:40] "type": "WebJobsAuthLevel",
[20/02/2020 22:10:40] "level": "Admin"
[20/02/2020 22:10:40] }
[20/02/2020 22:10:40] ],
[20/02/2020 22:10:40] "status": 202,
[20/02/2020 22:10:40] "duration": 1238
[20/02/2020 22:10:40] }
[20/02/2020 22:10:41] Executing 'SendDataFunction' (Reason='This function was programmatically called via the host APIs.', Id=01d35159-9266-4a2b-8cf2-592f208ba3d8)
[20/02/2020 22:10:41] Executed 'SendDataFunction' (Succeeded, Id=01d35159-9266-4a2b-8cf2-592f208ba3d8)
As you can see it has accepted the http POST request we sent and then went on to call our function with a reason. Perfect for testing!
Testing in Azure?
You’re now wondering, you’ve tested your timer trigger function locally and you’ve pushed it up to azure to a test/qa instance, and you want to manually run the timer trigger?
Now if we just change the @host
variable to our deployed function app (in my instance functionapp120200220100513.azurewebsites.net
).
@function = SendDataFunction
@host = functionapp120200220100513.azurewebsites.net
POST https://{{host}}/admin/functions/{{function}}
Content-Type: application/json
{
"input": null
}
And execute the http request we’ll get an 401
Unauthorized response back from the API, this is because once the Azure Functions app is deployed we don’t want anyone who has access to an internet connection to go trigger off any of our functions.
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer
Date: Thu, 20 Feb 2020 22:42:44 GMT
Connection: close
Content-Length: 0
These functions are now secured using host keys, and we can find these keys in the azure portal by navigating to the function app expanding our functions and clicking on manage.
If we copy the value for the _master
key we can add this to a x-functions-key
header on our request.
@function = SendDataFunction
@host = functionapp120200220100513.azurewebsites.net
@functionsKey = STX64cqIdG9Ic5rRDTs4fVgVvu2WTFUaEat9Vj3phpIE8dHNiSe9Ow==
POST https://{{host}}/admin/functions/{{function}}
Content-Type: application/json
x-functions-key: {{functionsKey}}
{
"input": null
}
Now if we execute the http request again we will get a 202
Accepted.
HTTP/1.1 202 Accepted
Date: Thu, 20 Feb 2020 22:59:29 GMT
Connection: close
Content-Length: 0