What is the simplest way to run a timer-triggered Azure Function locally once?

C#AzureTimerAzure Functions

C# Problem Overview


I have a few C# Azure Functions that run on a schedule using timer triggers. I've set them up like so, where %TimerSchedule% refers to a cron expression in the app settings:

public static void Run([TimerTrigger("%TimerSchedule%")]TimerInfo myTimer, TraceWriter log)

During development, I often want to run the functions locally using Azure Functions Tools for Visual Studio + Azure Functions Core Tools. But when I hit F5 to debug the function locally it (usually) doesn't run immediately. Instead, it starts waiting for the next occurrence as per the timer schedule. So for example, if my cron expression says to run daily at 8PM, I'd have to wait until 8PM for the function to actually run on my machine.

So my question is: What is the simplest and best way to make a function run once locally?

Things I have tried or considered:

  1. Use a more frequent timer schedule just for local development
    • This is OK but not perfect – you still have to wait a little bit unless it's very frequent, and if it's very frequent then the function might run multiple times. This is what I'm doing now.
  2. Write a console app or unit test that directly calls the function's Run() method
    • This isn't 100% straightforward because you have to provide TimerInfo and TraceWriter arguments to Run() – and I've found surprisingly little documentation for that.

Microsoft's Strategies for testing your code in Azure Functions page is not very helpful on this topic – it only mentions timer triggers as a way to test other trigger types.

In a perfect world, I'd hit F5 and the function would immediately run once – just like developing a "normal" .NET app.

C# Solutions


Solution 1 - C#

I had the same question, and used the DEBUG-flag to have the RunOnStartup only while debugging:

        public static void Run(
            [TimerTrigger("* 0 7 * * 1-5"
#if DEBUG
            , RunOnStartup=true
#endif
            )]TimerInfo myTimer, TraceWriter log)
        {

Solution 2 - C#

You could perhaps use the RunOnStartup flag as documented here. It doesn't quite meet your brief regarding it only running once, but it should at least execute it locally once the app has started.

> /// Gets or sets a value indicating whether the function should be invoked /// immediately on startup. After the initial startup run, the function will /// be run on schedule thereafter.

Example using attribute binding:

[TimerTrigger("%TimerSchedule%", RunOnStartup = true)]TimerInfo myTimer

Solution 3 - C#

From https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local?tabs=windows%2Ccsharp%2Cbash#non-http-triggered-functions

>### Non-HTTP triggered functions >For all kinds of functions other than HTTP triggers and webhooks, you can test your functions locally by calling an administration endpoint. Calling this endpoint with an HTTP POST request on the local server triggers the function. You can optionally pass test data to the execution in the body of the POST request. This functionality is similar to the Test tab in the Azure portal. > >You call the following administrator endpoint to trigger non-HTTP functions:

http://localhost:{port}/admin/functions/{function_name} >To pass test data to the administrator endpoint of a function, you must supply the data in the body of a POST request message. The message body is required to have the following JSON format:

{
    "input": "<trigger_input>"
}

Solution 4 - C#

If you are using VS Code, use the Azure Functions extension:

  1. Hit F5 to enter debug mode, this starts the function app.
  2. Go to the Azure icon in the Activity bar.
  3. Under Local Project, find the function you want to run, right click, and select "Execute Function Now".

Check out this MS quickstart guide.

Solution 5 - C#

Using postman should do the trick. Follow the below steps to Run or debug you Timer Trigger Locally.

1 . RUN your Project.

  1. Open Postman and past this url http://localhost:{port}/admin/functions/{function_name}

  2. Make sure to use a POST Method with Json body of { "input": "" }

  3. Press SEND.

You Should receive a response of 202.

Solution 6 - C#

I had the same question. I fixed it with a Unittest. Indeed you need to stub out the TraceWriter and the TimerInfo.

Here some code how I did this.

TimerInfo:

public class ScheduleStub : TimerInfo
{
    public ScheduleStub(TimerSchedule schedule, ScheduleStatus status, bool isPastDue = false) : base(schedule, status, isPastDue)
    {
    }
}

And the TraceWriter:

 public class TraceWriterStub : TraceWriter
{
    protected TraceLevel _level;
    protected List<TraceEvent> _traces;

    public TraceWriterStub(TraceLevel level) : base(level)
    {
        _level = level;
        _traces = new List<TraceEvent>();
    }

    public override void Trace(TraceEvent traceEvent)
    {
        _traces.Add(traceEvent);
    }

    public List<TraceEvent> Traces => _traces;
}

Solution 7 - C#

Start your function with this curl command

curl --request POST -H "Content-Type:application/json" --data '{"input":""}'  http://localhost:7071/admin/functions/{function_name}

The input data is required, without it the function won't be triggered.

Solution 8 - C#

Just add another function with HTTP trigger type within the same class, add your code, or call your Run method from that function and invoke it from your browser.

Be sure to comment/remove that function when deployed to prod, or you will have the ability to trigger the function via HTTP calls in prod.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionReilly WoodView Question on Stackoverflow
Solution 1 - C#BerDevView Answer on Stackoverflow
Solution 2 - C#Wah YuenView Answer on Stackoverflow
Solution 3 - C#reddi.ethView Answer on Stackoverflow
Solution 4 - C#Renel ChesakView Answer on Stackoverflow
Solution 5 - C#maxspanView Answer on Stackoverflow
Solution 6 - C#StefanHaView Answer on Stackoverflow
Solution 7 - C#Alexander van TrijffelView Answer on Stackoverflow
Solution 8 - C#Mike CardonaView Answer on Stackoverflow