Unit Tests for timer triggered Azure Function: provide test data - c#

To Unit Testing HTTP triggered Azure Functions with test data is well and often described. For example here:
How to write unit test for azure function v1
Mock data are given in the http request.
But I have a timer triggerd Azure function which reads data from a FTP server. I would like to Unit Test the function with test data.
My function:
[FunctionName("MyTimerTriggeredFunction")]
public static void Run([TimerTrigger("0 */2 * * * *")]TimerInfo myTimer, ILogger log, ExecutionContext context)
{
log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
ServiceClass serviceClass = new ServiceClass(log, context);
List<Order> orderList = serviceClass.getOrdersFromFtp();
...
a running Unit Test function to test the logger, just to show how I started:
public void TestLogger()
{
// https://learn.microsoft.com/de-de/azure/azure-functions/functions-test-a-function
var logger = (ListLogger)TestFactory.CreateLogger(LoggerTypes.List);
MyTimerTriggeredFunction.Run(null, logger, null);
var msg = logger.Logs[0];
bool testString = msg.Contains("C# Timer trigger function executed at");
Assert.IsTrue(testString);
}
serviceClass.getOrdersFromFtp() return a list of objects. I could create this list with mock data in the unit test but how to give it to the timer triggered azure function?

You should move your business logic outside of the azure function and test that, rather than coming up with ways to mock data for a timer-triggered function.
Your code would look something like this:
[FunctionName("MyTimerTriggeredFunction")]
public static void Run([TimerTrigger("0 */2 * * * *")]TimerInfo myTimer, ILogger log, ExecutionContext context)
{
log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
ServiceClass serviceClass = new ServiceClass(log, context);
serviceClass.DoWork();
...
class ServiceClass
{
public void DoWork()
{
List<Order> orderList = serviceClass.getOrdersFromFtp();
...

Related

Is it possible that 2 azure functions can get triggered by one eventhub?

Is it possible that 2 azure functions can get triggered by one eventhub? One azure function will write its data to database1 and the other azure function writes its data to database2
[FunctionName("EventToDB1")]
public async System.Threading.Tasks.Task Run([EventHubTrigger("eventhub", Connection = "Debezium")]
EventData[] events, ILogger log)
{
{
[FunctionName("EventToDB2")]
public async System.Threading.Tasks.Task Run([EventHubTrigger("eventhub", Connection = "Debezium")]
EventData[] events, ILogger log)
{
{
answer on the possibility of having 2 azure functions get triggered by one eventhub
Yes that is possible by using different consumer groups. Because you specified the same connection to the Event Hub, being "Debezium", I Assume you want both funtions to process the same message. You have to create a new consumer group and specify the name using the ConsumerGroup property of the EventHubTrigger attribute (The default consumergroup is $Default):
public class EventToDB1
{
[FunctionName("EventToDB1")]
public async System.Threading.Tasks.Task Run(
[EventHubTrigger("eventhub",
Connection = "Debezium",
ConsumerGroup = "CG1")]
EventData[] events, ILogger log)
{
}
}
public class EventToDB2
{
[FunctionName("EventToDB2")]
public async System.Threading.Tasks.Task Run(
[EventHubTrigger("eventhub",
Connection = "Debezium",
ConsumerGroup = "CG2")]
EventData[] events, ILogger log)
{
}
}
Each consumer group receives the same messages from the Event Hub.
I do agree with #peter bons, you need to create two consumer groups for that and you can create two consumers by below process:
You can also use logic apps to work with event hubs.

How to Set up QuartzNet to Execute Recurring Background Job?

In an MVC5 web application using Net 4.8, I am trying to do the following:
When launching the application, I would like a scheduler to trigger a job every 2 Minutes.
The job reads and processes messages from a message queue.
I previously used Hangfire for this and it worked quite well.
However, I was told not to use Hangfire for this application. As an alternative, I opted for Quartz.NET here but I am currently having trouble setting up and triggering
the desired action. Following the Quartz.NET documentation, this is my current setup.
public class DummyJob :IJob
{
private readonly ISomeInterface _someInterface;
public DummyJob(ISomeInterface someInterface)
{
_someInterface = someInterface;
}
public async Task Execute(IJobExecutionContext context)
{
await _someInterface.ProcessMessages();
}
}
Next, the job configuration
public class JobScheduler
{
public static void Start()
{
// Run every 2 minutes
const string cron = #"0 0/2 * * * ?";
ISchedulerFactory schedulerFactory = new StdSchedulerFactory();
var scheduler = (IScheduler)schedulerFactory.GetScheduler().Result;
scheduler.Start();
var job = JobBuilder.Create<DummyJob>().Build();
var trigger = TriggerBuilder
.Create()
.WithCronSchedule(cron)
.Build();
scheduler.ScheduleJob(job, trigger);
}
}
I tried starting the job from both Startup.cs and Global.asax. Although the job was created, it did not fire when launching the application.
public void Configuration(IAppBuilder app)
{
[...]
JobScheduler.JobScheduler.Start();
}
What am I missing in terms of setting up QuartzNet? Do I need to put JobScheduler.JobScheduler.Start(); into Global.asax.cs or Startup.cs?

Can durable functions have multiple triggers?

I have a durable function that is triggered once a day by a Timer Trigger:
[FunctionName("MyDurableFunction")]
public static async Task Run(
[TimerTrigger("0 0 23 * * *", RunOnStartup = false)] TimerInfo myTimer,
[OrchestrationClient] DurableOrchestrationClient starter,
ILogger log)
{
await starter.StartNewAsync("OrchestrationFunction", null);
}
[FunctionName("OrchestrationFunction")]
public static async Task OrchestrationFunction(
[OrchestrationTrigger]DurableOrchestrationContext context,
ILogger log)
{
// do stuff
}
This works fine. For testing purposes I would also like to be able to trigger the durable function via a Http Trigger, so I added this:
[FunctionName("MyDurableFunctionHttpTrigger")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "demo")]HttpRequest req,
[OrchestrationClient] DurableOrchestrationClient starter,
ILogger log)
{
await starter.StartNewAsync("OrchestrationFunction", null);
return new OkObjectResult("blah");
}
Running these locally, including either the http trigger or the timer trigger will trigger the function, but including both in the class means that neither trigger events will occur. Is it possible to have multiple trigger types start an orchestration trigger?
I believe you can only have one trigger type per function but can suggest you write all your logic in to a separate project/assembly and then just reference the assembly and call the entry point via parameters, keeping your function implementation clean and simple and centralising the execution logic in another project (or classes within the same project).
On your code, you should have Orchestrator and Activity functions, so you could write one Activity function to do the work and call it from two orchestrators. The guidance on Durable Functions is to keep the orchestrator clean and simple managing just that - the orchestration, offloading the work to the Activities.
I recommend you look at the durable monitor pattern for your timer based requirement and look at the HTTP APIs for HTTP Triggers.
What you could do is create multiple normal functions, one for each type of trigger. A scheduled trigger, http trigger, blob trigger, or any other supported trigger.
Within that function you can start a new orchestration function. That orchestration function does not require a trigger in itself. You only need the DurableOrchestrationContext.
public static async Task<object> RunOrchestrator(
[OrchestrationTrigger] DurableOrchestrationContext context,
ILogger log)
{
// orchestration logic here
}
[FunctionName("Info_HttpStart1")]
public static async Task<HttpResponseMessage> HttpStart(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "starter1")]HttpRequestMessage req,
[OrchestrationClient]DurableOrchestrationClient starter,
ILogger log)
{
// Function input comes from the request content.
string instanceId = await starter.StartNewAsync("Info", null);
log.LogInformation($"Started orchestration with ID = '{instanceId}'.");
return starter.CreateCheckStatusResponse(req, instanceId);
}
[FunctionName("Info_HttpStart2")]
public static async Task<HttpResponseMessage> HttpStart(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "starter2")]HttpRequestMessage req,
[OrchestrationClient]DurableOrchestrationClient starter,
ILogger log)
{
// Function input comes from the request content.
string instanceId = await starter.StartNewAsync("Info", null);
log.LogInformation($"Started orchestration with ID = '{instanceId}'.");
return starter.CreateCheckStatusResponse(req, instanceId);
}

Test Brokered Message

Now i'm working at writing unit test on azure service bus trigger function
It's highly needed to mock somehow BrokeredMessage object that pass around into function. Function declaration is given below:
public static void Run(
[ServiceBusTrigger("saas01.queue.dbmigration", AccessRights.Manage, Connection = "connection")]BrokeredMessage message)
Unfortunately, i can't find any applicable way to mock it. It hardly mocking du to this class is sealed and i can't event create wrapper around it. Do you have some ideas about it?
Thanks for helping
,
One solution is to create a wrapper around BrokeredMessage you can test, as is done here. Here's also a MSDN post to the ServiceBus team that talks about using a wrapper too.
Note that Azure Functions V2 uses the Message class, which is public and not sealed.
[FunctionName("ServiceBusFunc")]
public static void Run([ServiceBusTrigger("myqueue", AccessRights.Manage, Connection = "ServiceBus")]BrokeredMessage myQueueItem, TraceWriter log)
{
var message = new MyBrokeredMessage(myQueueItem);
BusinessLogic(message, log);
}
public static void BusinessLogic(MyBrokeredMessage myMessage, TraceWriter log)
{
var stream = myMessage.GetBody<Stream>();
var reader = new StreamReader(stream);
log.Info($"C# ServiceBus queue trigger function processed message: '{reader.ReadToEnd() }'");
}
public class MyBrokeredMessage
{
private BrokeredMessage _msg;
public MyBrokeredMessage(BrokeredMessage msg) => _msg = msg;
public T GetBody<T>()
{
return _msg.GetBody<T>();
}
}

How to configure WebJob ServiceBusTrigger retry policy

Edit: I will accept Azure configuration related changes as an answer to this question.
I am attempting to setup a retry policy to prevent instantly retrying a message when a 3rd party service is temporarily unavailable.
Currently the job is retried immediately multiple times and fails each time due to the temporary outage of the 3rd party service.
How do I set a retry delay for these messages?
I have the following code for Main:
class Program
{
static void Main()
{
var config = new JobHostConfiguration();
if (config.IsDevelopment)
config.UseDevelopmentSettings();
config.UseCore();
config.UseServiceBus(new ServiceBusConfiguration()
{
ConnectionString = Configuration.GetAppSetting("Microsoft.ServiceBus.ConnectionString"),
MessageOptions = new OnMessageOptions()
{
}
});
var host = new JobHost(config);
LogManager.GetCurrentClassLogger().Information("F1.Birst.Automation web job starting.");
// The following code ensures that the WebJob will be running continuously
host.RunAndBlock();
}
}
I have an ErrorMonitor setup which properly logs errors:
public class ExceptionHandler
{
private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
public void Handle([ErrorTrigger] TraceFilter message, TextWriter log)
{
foreach (var exception in message.GetEvents())
Log.Error(exception.Exception.InnerException, exception.Message);
}
}
And my message handler looks like this:
public class ChurchCodeChangedEventHandler : ChurchSpaceHandler
{
private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
public void Handle([ServiceBusTrigger(nameof(ChurchCodeChangedEvent), "F1.Birst.Automation.ChurchCodeChangedEvent")] ChurchCodeChangedEvent message, TextWriter log)
{
Log.Information(LogTemplates.ChurchCodeChanged, message.ChurchId);
// snip
}
}
How do I set a retry delay for these messages?
Webjobs do not support the concept of delayed retries. You can only control a few things using ServiceBusConfiguration, but those are not retries looking at the source code.
You could use frameworks like NServiceBus or MassTransit to get delayed retries. There's an example of how to use NServiceBus with WebJobs and you can run it locally to see how delayed retries would work.

Categories