Insight

Standard Logic App Unit Tests – VSCode Extension

A guide to Standard Logic App Unit Tests – VSCode Extension

A unit test is a block of code that verifies the accuracy of a smaller, isolated block of application code, typically a function or method. It’s designed to check that the block of code runs as expected, according to the developer’s theoretical logic behind it.

This guide shows how to create a unit test definition for a Standard Logic App workflow, in 4 different situations, taking in account 3 different types of workflows:
• Http-triggered workflow with a mapping and an external API call – httpTr
• Service Bus-triggered workflow with a mapping and an external API call – sbTr
• Http-triggered workflow with a condition clause – httpTrConditions

Prerequisites

  • VSCode
  • .Net Core Tools Extension
  • Azure Account Extension
  • Azure Logic App (Standard) extension

1 – Setting up a Logic App Workspace 

To start, we need to create a Logic App workspace by selecting the “Create new logic app workspace” option that is made available when we select the Azure tab on the left menu, as shown in the below image: 

 

We’ll then be prompted to choose the following settings:
• Workspace location;
• Workspace name;
• Select a project template (opt for Logic App);
• Logic App name
• Template for workflow (opt for Stateful workflow);
• Workflow name;

After this process, you should have a folder with your Logic App and respective workflow, which we’ll then manipulate to develop our unit tests.

2 – Http Triggered Workflow

We’ll start by creating a simple Logic App workflow composed of an http trigger, a Compose Action and an external API call, as suggested in Image 2.

As an example, we’re simulating to be a Food Stock Monitoring System used by a restaurant to track inventory levels. 

Below is an example of the Incoming Stock Notification:

				
					{ 
   "id": "msg_123456", 
   "timestamp": "2025-05-12T10:15:30Z", 
   "sender": "supplier@example.com", 
   "content": "Carrots", 
   "status": "delivered", 
   "priority": "normal" 
} 
 

				
			

The message will then be mapped by the Compose Action (renamed to Mapping) to return the following example output:

				
					 { 
   "productName":  “ @triggerBody()?['content']”, 
   "deliveryStatus": “ @triggerBody()?['status’]”, 
   "sourceWarehouse": “@triggerBody()?['sender’]”, 
   "messageTimestamp": “@triggerBody()?[' timestamp ']” 
} 

				
			

This message will then be sent through an external API call, which as an example will be the following one:

POST https://api.integration.team/v1/inventory/update 

Note: this API uri does not exists and calling this endpoint will return in a 404 HTTP error.

Content-Type: application/json

				
					{ 
   "productName":  “Carrots”, 
   "deliveryStatus": “delivered “, 
   "sourceWarehouse": “supplier@example.com”, 
   "messageTimestamp": “2025-05-12T10:15:30Z” 
} 

				
			

 

3  – Create Unit Test from workflow run

After completing the development of the workflow logic, we will proceed to implement unit tests to ensure each component behaves as expected in isolation.

The unit tests will simulate the behavior of the Logic App by:

  • Mocking the Trigger by injecting a predefined JSON payload to mimic incoming messages.
  • Mocking the External API Call: Rather than sending real HTTP requests to the external inventory API

By mocking both the input trigger and the external API call, we can validate that the workflow:

  • Parses and maps data correctly.
  • Constructs and sends the expected API request.

This approach enables safe and controlled testing of the Logic App’s internal logic without external dependencies.

The Azure Logic App (Standard) extension now includes a preview functionality to create Unit Tests from workflow runs, which makes this process easier to implement, and that we’ll be showing in the following steps: 

Step 1) Run the Workflow by hitting F5 or selecting the Start Debugging option 

 Step 2) Hover the workflow.json file, right click on it and select the option Overview

Step 3) A window like the image below will pop up, showing a Callback URL link to trigger the workflow.

Step 4) Use an API platform like Postman to make an http call to the Callback url. Send in the body sample that we provided as example  

Step 5) Click on the run that succeeded. A window of the run you just triggered should open.   

Step 6) Click on the option “Create Unit test from Run”. You will be prompted to choose a test name, which in this example can be “SuccessMessage

The unit tests will then be created under a directory called 

  • Tests/LogicAppName/WorkflowName

This directory will in turn contain 2 folders:

  • MockOutputs – containing each mockable operation in the workflow (it will only be created once for the workflow);
  • SuccessMessage – created for each test case, containing:
    • SuccessMessage-mock.json – a json file that represents the generated mocked operations in the workflow;
    • cs – a C# file containing the unit tests

Let’s now analyze one of the automatically generated Unit Tests present in the SuccessMessage.cs file: 

Unit Test 1

				
					[TestMethod]
public async Task httpTr_SuccessMessage_ExecuteWorkflow_SUCCESS_Sample1() { 
  // PREPARE Mock 
  // Generate mock data by loading SuccessMessage-mock.json file content   var mockData = this.GetTestMockDefinition(); 
   
  // ACT 
  // Create an instance of UnitTestExecutor and run the workflow with the mock data.   var testRun = await this.TestExecutor.Create() 
               .RunWorkflowAsync(testMock: mockData) 
               .ConfigureAwait(continueOnCapturedContext: false); 
 
  // ASSERT 
  // Verify that the workflow executed successfully, and the status is 'Succeeded'. 
  Assert.IsNotNull(value: testRun); 
  Assert.AreEqual(expected: TestWorkflowStatus.Succeeded, actual: testRun.Status); 
 
} 
 

				
			

The VSCode Logic App extension automatically generates this success case test having in account the run that we just triggered. 

By default, it generates 3 types of Unit Test structures:

  • Static Unit Tests – like the one shown above, using static mock data to test the workflow;
  • Dynamic Unit Tests – using either a callback method or an inline lambda function to dynamically generate mock data;
  • Error Scenario Tests – to test failure conditions.

We can easily adapt these generated unit test examples to our convenience thus covering the different scenarios of our Logic App workflow.           

 

4 – Mapping Assertion

One of the scenarios that are useful to adapt are the mapping assertions.

By testing specific operations, we’re making sure that we have more control over the behavior of our workflow and facilitating the identification of possible errors.

In our example, we have a mapping that is expected to behave like this:

				
					{ 
   "productName":  “ @triggerBody()?['content']”, 
   "deliveryStatus": “ @triggerBody()?['status’]”, 
   "sourceWarehouse": “@triggerBody()?['sender’]”, 
   "messageTimestamp": “@triggerBody()?[' timestamp ']” 
} 

				
			

We’ll use the same structure of the SuccessMessage Case that we used in the above section and adapt it, step by step:

Step 1) Preparing the mock data of our workflow

Unit Test 2

				
					[TestMethod]
public async Task httpTr_SuccessMessage_ExecuteWorkflow_SUCCESS_Sample1() { 
  // PREPARE Mock 
  // Generate mock data by loading SuccessMessage-mock.json file content   var mockData = this.GetTestMockDefinition(); 
   
  // Define expected output    var expectedMappingOutput = @”{ 
"" productName "": ""  Carrots "", 
       "" deliveryStatus "": "" delivered "", 
       "" sourceWarehouse "": "" supplier@example.com "", 
       "" messageTimestamp "": "" 2025-05-12T10:15:30Z "" 
  }”; 
   
  // Convert expected output to String 
  var expectedMappingOutputString = JsonConvert.SerializeObject( 
      JsonConvert.DeserializeObject(expectedMappingOutput), 
      Formatting.None); 

				
			

Step 2) Act stage

				
					  // ACT 
  // Run the workflow with the mock data   var testRun = await this.TestExecutor.Create() 
               .RunWorkflowAsync(testMock: mockData) 
               .ConfigureAwait(continueOnCapturedContext: false); 
 
  // Extract output of Mapping Action    var actualMappingOutput = testRun.Actions[“Mapping”].Outputs 
  // Convert output value to string 
  var actualMappingOutput = JsonConvert.SerializeObject( 
      JsonConvert.DeserializeObject(actualMappingOutput), 
      Formatting.None 
      ); 

				
			

Step 3) Assertion 

				
					 // ASSERT 
  // Verify that the workflow executed successfully and the mapping is executed as  expected 
  Assert.IsNotNull(value: testRun); 
  Assert.AreEqual(expected: expectedMappingOutputString,                   actual: actualMappingOutputString 
                  “JSON content does not match expected value in Mapping operation”); 
}  

				
			

An important aspect in step 2 is to know how to identify the name of the action whose output we want to extract. In this case, the name of the action is Mapping. We can identify this name in the workflow.json file, as shown in the image below.

5 – Service Bus triggered Workflow

If instead of an http-triggered workflow we decide to change our workflow to be service-bus triggered, we will not need to adapt the SuccessMessage.cs unit test code that we showed, but instead the SuccessMessage-mock.json file that we mentioned earlier, since the method RunWorkflowAsync(testMock: mockData) is dependent on the mock data that is injected through the json file. 

After proceeding in a similar way we did in Section 3 to produce Unit Test 3 for the sbTr workflow, we will make sure that the produced SuccessMessage-mock.json files are different.

So, while for the http-triggered json mock file has the following json structure: 

				
					"triggerMocks": { 
    "When_a_HTTP_request_is_received": { 
      "name": "When_a_HTTP_request_is_received", 
      "status": "Succeeded", 
      "outputs": { 
        "body": { 
          "id": "msg_123456", 
          "timestamp": "2025-05-12T10:15:30Z", 
          "sender": "user@example.com", 
          "content": "Test message", 
          "status": "delivered", 
          "priority": "normal" 
        } 
      } 

				
			

The service-bus triggered json mock file will look like this:

				
					  "triggerMocks": { 
    "When_messages_are_available_in_a_queue": { 
      "name": "When_messages_are_available_in_a_queue", 
      "status": "Succeeded", 
      "outputs": { 
        "body": { 
            "contentData": { 
                "id": "msg_123456", 
                "timestamp": "2025-05-12T10:15:30Z", 
                "sender": "user@example.com", 
                "content": "Test message", 
                "status": "delivered", 
                "priority": "normal" 
            } 
         } 


				
			

6 – Workflow with conditions and dynamic Unit Test

Let’s now imagine that our Food Stock Monitoring System Logic App workflow includes a condition to check for high priority products. This means that, if the input payload has the priority field identified as urgent, that priority should be passed through in another mapping, so that the stock manager is aware of it.
We will then add a parallel Compose action which is slightly different from the one before:

				
					{ 
   "productName":  “ @triggerBody()?['content']”, 
   "deliveryStatus": “ @triggerBody()?['status’]”, 
   "sourceWarehouse": “@triggerBody()?['sender’]”, 
   "messageTimestamp": “@triggerBody()?[' timestamp ']”, 
   "priorityFlag": “@triggerBody()?[' priority ']”, 
}  

				
			

And add a condition in the workflow, as shown in the below image: 

In the previous version of the Logic App, with only one mapping, a static unit test was enough since it only needed to check that the API call was made successfully.

However, the updated workflow includes a condition: if the priority is “high”, the Logic App adds a “priorityFlag”: “urgent” field in the API request. This means the workflow behaves differently depending on the input.

To test this properly, a dynamic unit test is more appropriate, because it allows us to inspect the actual data being sent in the API call and verify that the workflow applied the correct mapping based on the input. For example, it can check that “priorityFlag” is present only when it should be.

Dynamic tests help ensure not just that the workflow ran, but that it made the right decisions.

This time we won’t resort to the “Create Unit test from Run”, but instead to the Create unit test option in Designer window. 

After naming your test – in this case we’ll name it Conditions – 2 directories will be created:

  • MockOutputs – containing each mockable operation in the workflow;
  • Conditions – which will in turn contain the C# class cs with Unit Tests. The file Conditions-mock.json will not be created this time because we don’t resort to the option of creating a test from a run, thus not having sample payloads to work with.

Inside the Conditions.cs file, a Unit Test called           public async Task httpTrCondition_Conditions_ExecuteWorkflow_SUCCESS_Sample2()

will have been created. This is the basis for the dynamic unit test, which we will adapt in the following way:     

Unit Test 4

				
					[TestMethod] 
public async Task httpTrCondition_Conditions_ExecuteWorkflow_SUCCESS_Sample2() { 
  // PREPARE Mock 
  // Generate mock trigger data.   var triggerMockOutput = new WhenAHTTPRequestIsReceivedTriggerOutput 
  { 
      Body = new WhenAHTTPRequestIsReceivedTriggerOutputBody 
     { 
           Id = "msg_123456", 
           Timestamp = "2025-05-12T10:15:30Z", 
           Sender = "supplier@example.com", 
           Content = "Carrots", 
           Status = "delivered", 
           Priority = "urgent" } 
   }; 
  // Inject mock sample   var triggerMock = new WhenAHTTPRequestIsReceivedTriggerMock(        outputs: triggerMockOutput 
       ); 
  // Generate action mock data   var actionMock = new ExternalAPICallActionMock(         name: "External_API_call",         onGetActionMock: ExternalAPICallActionMockOutputCallback 
      );  
  // ACT 
  // Create instance of UnitTestExecuter with injected triggerMock and actionMock   var testMock = new TestMockDefinition(        triggerMock: triggerMock, 
       actionMocks: new Dictionary<string, ActionMock>() 
       { 
          {actionMock.Name, actionMock} 
       }); 
 // Run the workflow with mock data  var testRun = await this.TestExecutor 
     .Create() 
     .RunWorkflowAsync(testMock: testMock) 
     .ConfigureAwait(continueOnCapturedContext:false); 
 
 // ASSERT 
 // Verify that the workflow executed successfully, and the status is 'Succeeded'. 
 Assert.IsNotNull(value: testRun); 
 Assert.AreEqual(expected: TestWorkflowStatus.Succeeded, actual: testRun.Status); 

				
			

The main difference when comparing to the Static Unit Test is that we’re generating an actionMock variable, which becomes a mock version of the external API call through the ExternalAPICallActionMockOutputCallback method.

Let’s now analyze this method to understand what it does:

				
					public ExternalAPICallActionMock ExternalAPICallActionMockOutputCallback (TestExecutionContext context) 
{ 
  // Read the full input payload passed to the External_API_call action   var apiInput = context.ActionContext?.ActionInputs?["body"] as JObject;  
  // Make sure the value of apiInput is not null 
  Assert.IsNotNull(apiInput, "Expected action input body to not be null.");  
  // Read the value of productName to a variable   var productName = apiInput["productName"]?.ToString();   // Read the value of priorityFlag to a variable (if it exists)   var priorityFlag = apiInput["priorityFlag"]?.ToString(); 
   if (priorityFlag != null) 
     { 
     // If priorityFlag exists, we expect it to be "urgent"  
     Assert.AreEqual("urgent", priorityFlag, 
          $"Expected 'priorityFlag' to be ‘urgent’ for product: {productName}"); 
     }   else 
    { 
     // If it does not exist, meaning it’s a non-priority item, the test passes 
     Assert.IsTrue(true, 
         "Normal priority:'priorityFlag' is not present as expected."); 
    } 
   
  // Return a mock success response to simulate the API response   return new ExternalAPICallActionMock 
    ( 
     status: TestWorkflowStatus.Succeeded,      outputs: new ExternalAPICallActionOutput() 
     ); 
}  

				
			

As we can see, we’re adding a new layer of assertion by analyzing the input of the external api call and failing if it does not include what is expected at that stage of the workflow.  This callback function can be used to validate the 2 paths that this workflow can take in case of urgent or non-urgent stock notification. 

 

7  – Running the Unit Tests

After having developed the Unit Tests with the support of the preview functionality of VSCode Logic App extension, it’s time to run the tests and understand how to take advantage of them.

As a reminder, we have built the following 4 Unit Tests:

  • Unit Test 1 – Success message with an http-triggered workflow
  • Unit Test 2 – Mapping Assertion
  • Unit Test 3 – Success message with a service-bus triggered workflow
  • Unit Test 4 – Workflow with conditions

 Right now, the workspace folder organization should follow a structure similar to this:

In which StdLAUnitTests refers to the name of the LogicApp, the httpTr folder contains Unit Tests 1 and 2, the sbTr folder contains the Unit Test 3 and the httpTrCondition one includes the Unit Test 4

Running the Unit Tests is a quite straightforward process. We open a new terminal inside the Tests directory and run the command: dotnet test The success message will look like this: 

If instead we want to run a specific unit test, we add the flag filter followed by the unit test name. For instance, for the case of Unit Test 1, it would look like this: dotnet test -–filter httpTr_SuccessMessage_ExecuteWorkflow_SUCCESS_Sample1

Now let’s try to provoke a unit test error to see how it looks like. We will not change the Unit Test code, but instead implement an unexpected behaviour in the workflow and see what happens to its respective unit test.

Let’s take the httpTrCondition and change the mapping of the priority case as the image below suggests:

If we now run the Unit Test 4, we’ll get the following output:


				
					Failed httpTrCondition_Conditions_ExecuteWorkflow_SUCCESS_Sample2 [3 s] 
  Error Message: Test method 
StdLAUnitTests.Tests.Conditions.httpTrCondition_Conditions_ExecuteWorkflow_SUCCESS_Sa mple2 threw exception: 
System.ArgumentException: Assert.AreEqual failed. Expected:<urgent>. Actual:<ERROR>. 
Expected 'priorityFlag' to be 'urgent' for product: Carrots 

				
			

As we can see, the assertion that is present in the callback function introduced in section 5 signals an error, indicating that an unexpected “ERROR” string was mapped, instead of the desired “urgent” value. 

This example illustrates the benefits that unit tests can provide, by signaling typos or other errors in the logic of the workflow, thus preventing the proliferation of errors in an early stage of code development.

        

8 – Unit Tests in Devops

In this last section of this tutorial, we will show how to integrate the Unit Tests we just generated in an Azure pipeline.

We will start by creating a new directory called Pipelines in the root directory, and including it in the workspace file that should be located in the same directory under the name:

[WorkspaceName].code-workspace

We will include the new directory in this file so that it can show in our vscode workspace. Here’s the necessary adaptation: 

Now counting with this new directory in our VSCode folder view, we can include in it the following 2 files:

  • azure-pipelines.yml – a yaml file that includes
  • settings.template.json – a local settings file that will be needed for the pipeline unit tests and will not include sensitive information.

 

Let’s now take a look at the code of azure-pipelines.yml file and its respective remarks: 

				
					# pipeline trigger trigger: 
-	main 
# use the latest windows-based build agent pool:   vmImage: 'windows-latest'  # define pipeline-level variables variables:   buildConfiguration: 'Release' # Or 'Debug' 
  testProject: 'Tests/StdLAUnitTests/StdLAUnitTests.csproj' steps: 
  # Step 1: Install the .NET SDK (version 6.x) 
-	task: UseDotNet@2     inputs: 
      packageType: 'sdk'       version: '6.x’ 
 
  # Step 2: Restore required NuGet packages 
-	task: DotNetCoreCLI@2     displayName: 'Restore packages'     inputs:       command: 'restore'       projects: '$(testProject)' 
 
  # Step 3: Generate a local.settings.json file from a template 
-	task: PowerShell@21111111 
    displayName: 'Generate local.settings.json'     inputs:       targetType: 'inline'       script: |         $templatePath = 
"$(Build.SourcesDirectory)/Pipelines/local.settings.template.json" 
        $outputPath = "StdLAUnitTests/local.settings.json" 
        $content = Get-Content $templatePath -Raw 
        $dir = Split-Path -Path $outputPath -Parent         # create output directory if it does not exist  
        if (!(Test-Path -Path $dir)) { New-Item -Path $dir -ItemType Directory } 
        # write content of local settings file to generated file 
        $content | Set-Content -Path $outputPath -Encoding utf8 
 
  # Step 4: Run the Unit Tests   - task: DotNetCoreCLI@2     displayName: 'Run tests'     inputs: 
      command: 'test'       projects: '$(testProject)' 
      arguments: '--configuration $(buildConfiguration) 
      publishTestResults: true #publishes test results to Azure Deveops test report 

				
			

The local.settings.template.json file content will look like this:

				
					{ 
  "IsEncrypted": false, 
  "Values": { 
    "AzureWebJobsStorage": "UseDevelopmentStorage=true", 
    "FUNCTIONS_WORKER_RUNTIME": "node", 
    "APP_KIND": "workflowapp" 
  } 
} 

				
			

After deploying and running the pipeline, we should get an output like this:

The output of the Run Tests task is the same as the one obtained in our local terminal, allowing us to identify a possible error in our workflow. 

As an example, we can also induce the workflow to error in image 9. The output will return as follows: 

As we can see the pipeline fails, which means that if, for instance, we place this pipeline Job before a Logic App deployment Job, we can prevent it from being deployed in the Azure Portal with errors, thus providing an early diagnosis. 

We can also observe that these tests were published in the Test Runs tab, giving us a percentage of successful Unit Tests (pass rate). In this case, 1 of the 4 tests failed in the second run, resulting in a pass rate of 75%, as show in the image below. 

 

 

By Rafael Simoes (CBX Portugal)

Other .insights

Privacy Overview

This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.