83
votes

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.

8

8 Answers

82
votes

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)
        {
87
votes

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

57
votes

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>"
}
12
votes

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.

6
votes

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.

5
votes

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;
}
1
votes

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.

0
votes

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.