Unit Testing Low Code Logic Apps Standard Workflows

Microsoft recently introduced, in preview, the ability to create unit tests for Logic Apps Standard workflows defined in an MSTest project.

This significantly improves the development experience when writing and maintaining low-code Logic Apps workflows. It enables developers to:

  • Write tests in C# using tools integrated into the IDE via the Logic Apps Standard VS Code extension. Writing tests in C# is crucial as it allows the use of custom utilities and useful NuGet packages.
  • Execute tests in a CI/CD pipeline.

Both practices are well-known to developers and are considered industry best practices. Without automated testing, teams risk undetected bugs, slower feedback loops, longer release cycles, and compromised software quality.

This new feature offers a more robust approach to developing and testing Logic Apps workflows, which is likely to boost its adoption.

Microsoft provides documentation on creating unit tests for workflows in VS Code, either from the workflow definition or from a workflow run. I won’t repeat the details here, but the main points are:

  • The unit of testing is the entire workflow, with mock objects injected into it.
  • The unit test wizard generates mock types for the workflow’s trigger and actions that depend on external systems (e.g., HTTP, Service Bus, Files, SAP, etc.).
  • Mock object instances can be created either programmatically in C# or in a mock definition JSON file. The latter being a serialization of the Mock objects. Note that when creating unit test from a workflow run, the JSON file is generated by the unit test wizard with data taken from the run instance.
  • Unit tests are written as C# methods decorated with the [TestMethod] attribute.

Implementing Negative Tests

For the workflow I wrote unit tests for, I did not encounter any limitations for happy path scenarios. However, I quickly came across a limitation when writing negative tests.

To illustrate this, let’s consider the simple workflow below, which calls an HTTP endpoint through the HTTP Action. Depending on the success or failure of this call, the business process takes different paths. To simplify the illustration, I replaced these different paths with returning different responses in the Response actions.

When generating unit tests from a successful run, the wizard creates a negative test like this:


[TestMethod]
public async Task GetGreetings_GetGreetingsSuccess_ExecuteWorkflow_FAILED_Sample3()
{
    // PREPARE
    var mockData = this.GetTestMockDefinition();
    var mockError = new TestErrorInfo(code: ErrorResponseCode.BadRequest, message: "Input is invalid.");
    mockData.ActionMocks["HTTP"] = new HTTPActionMock(status: TestWorkflowStatus.Failed, error: mockError);

    // ACT
    var testRun = await this.TestExecutor
        .Create()
        .RunWorkflowAsync(testMock: mockData).ConfigureAwait(false);

    // ASSERT
    Assert.IsNotNull(testRun);
    Assert.AreEqual(TestWorkflowStatus.Failed, testRun.Status);
}

Note that the HTTPActionMock type models the mock for the workflow’s HTTP action. The code above overrides the ActionMock for the HTTP action loaded from the JSON file with a mock having its status set to Failed instead of Succeeded. As the HTTP action is now set to fail, it will cause the entire workflow to fail.

Now, let’s imagine that I want to enhance the test and ensure that when the HTTP action fails, the “Response OK” action does not run, and the “Response Failure” action runs instead. To implement this, I can simply add:


Assert.AreEqual(expected: TestWorkflowStatus.Skipped, actual: testRun.Actions["Response_OK"].Status);
Assert.AreEqual(expected: TestWorkflowStatus.Succeeded, actual: testRun.Actions["Response_Failure"].Status);

These asserts will ensure that I have implemented my business logic correctly and also acts as a regression test to detect if a breaking change is introduced later on.

Current Limitations

Current Limitation with Negative Testing

In more complex scenarios, business logic might depend on the actual content of the error response returned by the HTTP call. For example, a web API might return specific error codes.

I expect to implement such a scenario by overriding the Action Mock for the HTTP action with:

  • A failed status
  • An output for the mock with the specific payload returned by the HTTP action

I tried defining this in C#:


var actionOutput = new HTTPActionOutput
{
    Body = new JObject { ["errorCode"] = "009" },
    StatusCode = HttpStatusCode.BadRequest
};

var httpFailedActionMock = new HTTPActionMock(
    status: TestWorkflowStatus.Failed,
    outputs: actionOutput
);

mockData.ActionMocks["HTTP"] = httpFailedActionMock;

But this causes the TestExecutor to throw the following exception:

The workflow '' associated with unit test '' has action mock 'HTTP' that should have non empty error message when status is set to 'Failed'.

The only way to prevent the exception is to use a constructor that takes a TestErrorInfo object — but this constructor does not allow passing a custom HTTP response payload, which prevents me from implementing my test scenario.

Even trying to bypass this limitation by editing the JSON file directly did not work:

"actionMocks": {
  "HTTP": {
    "name": "HTTP",
    "status": "Failed",
    "outputs": {
      "statusCode": 400,
      "body": {
        "errorCode": "009"
      }
    },
    "error": {
      "Code": "BadRequest",
      "Message": "The request is invalid."
    }
  }
}

Current Limitation with MSTest project

As of now, the MSTest project must target .NET 6.0, which is no longer supported by Microsoft. Although this code is only used for testing and not deployed to production, some company security policies may still flag it. Static analysis tools like SonarQube, Snyk, and others might raise alerts due to the use of an unsupported framework, requiring documented exceptions and justification.

Conclusion

Given that this is the initial public preview, I’m satisfied with the current capabilities and have provided feedback to Microsoft, expressing hope for improvements in negative test handling and support for a more recent version of .NET.

Leave a Reply

Your email address will not be published. Required fields are marked *


*