Skip to content

Commit ccc971a

Browse files
committed
feat: Add support for custom configuration files in test projects
Add optional parameters to Initialize() method allowing tests to use custom-named configuration files (local.settings.json, parameters.json, connections.json) located in the test project instead of the Logic App project. Key Features: - Optional parameters: localSettingsFilename, parametersFilename, connectionsFilename - File resolution checks test project first, then falls back to Logic App project - Enables unified test suites across environments (DEV/QA/PROD) using the same test code with different configuration files - Maintains full backward compatibility with existing tests Changes: - Added Initialize() method overload with optional configuration parameters - Added ResolveConfigurationFilePath() helper method for file resolution - Updated ProcessLocalSettingsFile(), ProcessParametersFile(), and ProcessConnectionsFile() to accept custom filenames - Created example test classes demonstrating the feature - Updated project version to 1.13.0 Examples: - parameters-dev.json, parameters-qa.json, parameters-prod.json - local.settings-custom.json, connections-custom.json
1 parent 1907275 commit ccc971a

9 files changed

Lines changed: 696 additions & 12 deletions

File tree

ChangeLog.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
# 1.13.0 (4th December 2025)
2+
3+
LogicAppUnit Testing Framework:
4+
5+
- Added support for custom configuration files in test projects. The `Initialize()` method now accepts optional parameters to specify custom filenames for `local.settings.json`, `parameters.json`, and `connections.json`. Configuration files are resolved by checking the test project directory first, then falling back to the Logic App project directory. This allows tests to use test-specific configurations that override the Logic App project defaults. This enables unified test suites across environments - use the same test code for DEV, QA, and PROD by simply swapping configuration files (e.g., `parameters-dev.json`, `parameters-qa.json`, `parameters-prod.json`).
6+
7+
LogicAppUnit.Samples.LogicApps.Tests:
8+
9+
- Added example custom configuration files (`local.settings-custom.json`, `parameters-custom.json`, `connections-custom.json`) in the test project.
10+
- Added `HttpWorkflowCustomConfigTest`, `BuiltInConnectorWorkflowCustomConfigTest`, and `ManagedApiConnectorWorkflowCustomConfigTest` to demonstrate the custom configuration file feature.
11+
12+
113
# 1.12.0 (18th July 2025)
214

315
LogicAppUnit Testing Framework:
Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
using LogicAppUnit.Helper;
2+
using LogicAppUnit.Mocking;
3+
using Microsoft.VisualStudio.TestTools.UnitTesting;
4+
using Newtonsoft.Json.Linq;
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
using System.Net;
9+
using System.Net.Http;
10+
11+
namespace LogicAppUnit.Samples.LogicApps.Tests.HttpWorkflow
12+
{
13+
/// <summary>
14+
/// Test cases for the <i>http-workflow</i> workflow using custom configuration files.
15+
/// This test class demonstrates the custom configuration file replacement feature.
16+
/// All tests are identical to HttpWorkflowTest, but use custom parameters, connections, and local settings files.
17+
/// </summary>
18+
[TestClass]
19+
public class HttpWorkflowCustomConfigTest : WorkflowTestBase
20+
{
21+
private const string _WebHookRequestApiKey = "serviceone-auth-webhook-apikey";
22+
23+
[TestInitialize]
24+
public void TestInitialize()
25+
{
26+
// Using custom configuration files to test the replacement feature
27+
// Files will be loaded from test project if they exist, otherwise from Logic App project
28+
Initialize(
29+
Constants.LOGIC_APP_TEST_EXAMPLE_BASE_PATH,
30+
Constants.HTTP_WORKFLOW,
31+
localSettingsFilename: "local.settings-custom.json",
32+
parametersFilename: "parameters-custom.json"
33+
);
34+
}
35+
36+
[ClassCleanup]
37+
public static void CleanResources()
38+
{
39+
Close();
40+
}
41+
42+
/// <summary>
43+
/// Tests that the correct response is returned when an incorrect value for the 'X-API-Key header' is used with the webhook request.
44+
/// </summary>
45+
[TestMethod]
46+
public void HttpWorkflowCustomConfigTest_When_Wrong_API_Key_In_Request()
47+
{
48+
using (ITestRunner testRunner = CreateTestRunner())
49+
{
50+
// Run the workflow
51+
var workflowResponse = testRunner.TriggerWorkflow(
52+
GetWebhookRequest(),
53+
HttpMethod.Post,
54+
new Dictionary<string, string> { { "x-api-key", "wrong-key" } });
55+
56+
// Check workflow run status
57+
Assert.AreEqual(WorkflowRunStatus.Succeeded, testRunner.WorkflowRunStatus);
58+
59+
// Check workflow response
60+
testRunner.ExceptionWrapper(() => Assert.AreEqual(HttpStatusCode.Unauthorized, workflowResponse.StatusCode));
61+
Assert.AreEqual("Invalid/No authorization header passed", workflowResponse.Content.ReadAsStringAsync().Result);
62+
Assert.AreEqual("text/plain; charset=utf-8", workflowResponse.Content.Headers.ContentType.ToString());
63+
64+
// Check action result
65+
Assert.AreEqual(ActionStatus.Succeeded, testRunner.GetWorkflowActionStatus("Unauthorized_Response"));
66+
Assert.AreEqual(ActionStatus.Skipped, testRunner.GetWorkflowActionStatus("Get_Customer_Details_from_Service_One"));
67+
}
68+
}
69+
70+
/// <summary>
71+
/// Tests that the correct response is returned when the HTTP call to the Service One API to get the customer details fails.
72+
/// </summary>
73+
[TestMethod]
74+
public void HttpWorkflowCustomConfigTest_When_Get_Customer_Details_Fails()
75+
{
76+
using (ITestRunner testRunner = CreateTestRunner())
77+
{
78+
// Configure mock responses
79+
testRunner
80+
.AddMockResponse(
81+
MockRequestMatcher.Create()
82+
.UsingGet()
83+
.WithPath(PathMatchType.Exact, "/api/v1/customers/54617"))
84+
.RespondWith(
85+
MockResponseBuilder.Create()
86+
.WithInternalServerError()
87+
.WithContentAsPlainText("Internal server error detected in System One"));
88+
89+
// Run the workflow
90+
var workflowResponse = testRunner.TriggerWorkflow(
91+
GetWebhookRequest(),
92+
HttpMethod.Post,
93+
new Dictionary<string, string> { { "x-api-key", _WebHookRequestApiKey } });
94+
95+
// Check workflow run status
96+
Assert.AreEqual(WorkflowRunStatus.Succeeded, testRunner.WorkflowRunStatus);
97+
98+
// Check workflow response
99+
testRunner.ExceptionWrapper(() => Assert.AreEqual(HttpStatusCode.InternalServerError, workflowResponse.StatusCode));
100+
Assert.AreEqual("Unable to get customer details: Internal server error detected in System One", workflowResponse.Content.ReadAsStringAsync().Result);
101+
Assert.AreEqual("text/plain; charset=utf-8", workflowResponse.Content.Headers.ContentType.ToString());
102+
103+
// Check action result
104+
Assert.AreEqual(ActionStatus.Skipped, testRunner.GetWorkflowActionStatus("Unauthorized_Response"));
105+
Assert.AreEqual(ActionStatus.Failed, testRunner.GetWorkflowActionStatus("Get_Customer_Details_from_Service_One"));
106+
Assert.AreEqual(ActionStatus.Succeeded, testRunner.GetWorkflowActionStatus("Failed_Get_Response"));
107+
Assert.AreEqual(ActionStatus.Skipped, testRunner.GetWorkflowActionStatus("Update_Customer_Details_in_Service_Two"));
108+
}
109+
}
110+
111+
/// <summary>
112+
/// Tests that the correct response is returned when the HTTP call to the Service Two API to update the customer details fails.
113+
/// </summary>
114+
[TestMethod]
115+
public void HttpWorkflowCustomConfigTest_When_Update_Customer_Fails()
116+
{
117+
// Override one of the settings in the local settings file
118+
var settingsToOverride = new Dictionary<string, string>() { { "ServiceTwo-DefaultAddressType", "physical" } };
119+
120+
using (ITestRunner testRunner = CreateTestRunner(settingsToOverride))
121+
{
122+
// Configure mock responses
123+
testRunner
124+
.AddMockResponse(
125+
MockRequestMatcher.Create()
126+
.UsingGet()
127+
.WithPath(PathMatchType.Exact, "/api/v1/customers/54617"))
128+
.RespondWith(
129+
MockResponseBuilder.Create()
130+
.WithSuccess()
131+
.WithContent(GetCustomerResponse));
132+
testRunner
133+
.AddMockResponse(
134+
MockRequestMatcher.Create()
135+
.UsingPut()
136+
.WithPath(PathMatchType.Exact, "/api/v1.1/membership/customers/54617"))
137+
.RespondWith(
138+
MockResponseBuilder.Create()
139+
.WithInternalServerError()
140+
.WithContentAsPlainText("System Two has died"));
141+
142+
// Run the workflow
143+
var workflowResponse = testRunner.TriggerWorkflow(
144+
GetWebhookRequest(),
145+
HttpMethod.Post,
146+
new Dictionary<string, string> { { "x-api-key", _WebHookRequestApiKey } });
147+
148+
// Check workflow run status
149+
Assert.AreEqual(WorkflowRunStatus.Succeeded, testRunner.WorkflowRunStatus);
150+
151+
// Check workflow response
152+
testRunner.ExceptionWrapper(() => Assert.AreEqual(HttpStatusCode.InternalServerError, workflowResponse.StatusCode));
153+
Assert.AreEqual("Unable to update customer details: System Two has died", workflowResponse.Content.ReadAsStringAsync().Result);
154+
Assert.AreEqual("text/plain; charset=utf-8", workflowResponse.Content.Headers.ContentType.ToString());
155+
156+
// Check action result
157+
Assert.AreEqual(ActionStatus.Skipped, testRunner.GetWorkflowActionStatus("Unauthorized_Response"));
158+
Assert.AreEqual(ActionStatus.Succeeded, testRunner.GetWorkflowActionStatus("Get_Customer_Details_from_Service_One"));
159+
Assert.AreEqual(ActionStatus.Failed, testRunner.GetWorkflowActionStatus("Update_Customer_Details_in_Service_Two"));
160+
Assert.AreEqual(ActionStatus.Succeeded, testRunner.GetWorkflowActionStatus("Failed_Update_Response"));
161+
162+
// Check request to System Two Membership API
163+
var systemTwoRequest = testRunner.MockRequests.First(r => r.RequestUri.AbsolutePath == "/api/v1.1/membership/customers/54617");
164+
Assert.AreEqual(HttpMethod.Put, systemTwoRequest.Method);
165+
Assert.AreEqual("application/json", systemTwoRequest.ContentHeaders["Content-Type"].First());
166+
Assert.AreEqual("ApiKey servicetwo-auth-apikey", systemTwoRequest.Headers["x-api-key"].First());
167+
Assert.AreEqual(
168+
ContentHelper.FormatJson(ResourceHelper.GetAssemblyResourceAsString($"{GetType().Namespace}.MockData.SystemTwo_Request.json")),
169+
ContentHelper.FormatJson(systemTwoRequest.Content));
170+
171+
JToken parseCustomerInput = testRunner.GetWorkflowActionInput("Parse_Customer");
172+
JToken parseCustomerOutput = testRunner.GetWorkflowActionOutput("Parse_Customer");
173+
Assert.IsNotNull(parseCustomerInput.ToString());
174+
Assert.IsNotNull(parseCustomerOutput.ToString());
175+
}
176+
}
177+
178+
/// <summary>
179+
/// Tests that the correct response is returned when the HTTP call to the Service Two API to update the customer details is successful.
180+
/// </summary>
181+
[TestMethod]
182+
public void HttpWorkflowCustomConfigTest_When_Successful()
183+
{
184+
// Override one of the settings in the local settings file
185+
var settingsToOverride = new Dictionary<string, string>() { { "ServiceTwo-DefaultAddressType", "physical" } };
186+
187+
using (ITestRunner testRunner = CreateTestRunner(settingsToOverride))
188+
{
189+
// Configure mock responses
190+
testRunner
191+
.AddMockResponse(
192+
MockRequestMatcher.Create()
193+
.UsingGet()
194+
.WithPath(PathMatchType.Exact, "/api/v1/customers/54617"))
195+
.RespondWith(
196+
MockResponseBuilder.Create()
197+
.WithSuccess()
198+
.WithContent(GetCustomerResponse));
199+
testRunner
200+
.AddMockResponse(
201+
MockRequestMatcher.Create()
202+
.UsingPut()
203+
.WithPath(PathMatchType.Exact, "/api/v1.1/membership/customers/54617"))
204+
.RespondWith(
205+
MockResponseBuilder.Create()
206+
.WithSuccess()
207+
.WithContentAsPlainText("success"));
208+
209+
// Run the workflow
210+
var workflowResponse = testRunner.TriggerWorkflow(
211+
GetWebhookRequest(),
212+
HttpMethod.Post,
213+
new Dictionary<string, string> { { "x-api-key", _WebHookRequestApiKey } });
214+
215+
// Check workflow run status
216+
Assert.AreEqual(WorkflowRunStatus.Succeeded, testRunner.WorkflowRunStatus);
217+
218+
// Check workflow response
219+
testRunner.ExceptionWrapper(() => Assert.AreEqual(HttpStatusCode.OK, workflowResponse.StatusCode));
220+
Assert.AreEqual("Webhook processed successfully", workflowResponse.Content.ReadAsStringAsync().Result);
221+
Assert.AreEqual("text/plain; charset=utf-8", workflowResponse.Content.Headers.ContentType.ToString());
222+
223+
// Check action result
224+
Assert.AreEqual(ActionStatus.Skipped, testRunner.GetWorkflowActionStatus("Unauthorized_Response"));
225+
Assert.AreEqual(ActionStatus.Succeeded, testRunner.GetWorkflowActionStatus("Get_Customer_Details_from_Service_One"));
226+
Assert.AreEqual(ActionStatus.Succeeded, testRunner.GetWorkflowActionStatus("Update_Customer_Details_in_Service_Two"));
227+
Assert.AreEqual(ActionStatus.Skipped, testRunner.GetWorkflowActionStatus("Failed_Update_Response"));
228+
Assert.AreEqual(ActionStatus.Succeeded, testRunner.GetWorkflowActionStatus("Success_Response"));
229+
230+
// Check request to System Two Membership API
231+
var systemTwoRequest = testRunner.MockRequests.First(r => r.RequestUri.AbsolutePath == "/api/v1.1/membership/customers/54617");
232+
Assert.AreEqual(HttpMethod.Put, systemTwoRequest.Method);
233+
Assert.AreEqual("application/json", systemTwoRequest.ContentHeaders["Content-Type"].First());
234+
Assert.AreEqual("ApiKey servicetwo-auth-apikey", systemTwoRequest.Headers["x-api-key"].First());
235+
Assert.AreEqual(
236+
ContentHelper.FormatJson(ResourceHelper.GetAssemblyResourceAsString($"{GetType().Namespace}.MockData.SystemTwo_Request.json")),
237+
ContentHelper.FormatJson(systemTwoRequest.Content));
238+
239+
// Check tracked properties
240+
var trackedProps = testRunner.GetWorkflowActionTrackedProperties("Get_Customer_Details_from_Service_One");
241+
Assert.AreEqual("customer", trackedProps["recordType"]);
242+
Assert.AreEqual("54617", trackedProps["recordId"]);
243+
Assert.AreEqual("c2ddb2f2-7bff-4cce-b724-ac2400b12760", trackedProps["correlationId"]);
244+
}
245+
}
246+
247+
private static StringContent GetWebhookRequest()
248+
{
249+
return ContentHelper.CreateJsonStringContent(new
250+
{
251+
id = "71fbcb8e-f974-449a-bb14-ac2400b150aa",
252+
correlationId = "c2ddb2f2-7bff-4cce-b724-ac2400b12760",
253+
sourceSystem = "SystemOne",
254+
timestamp = "2022-08-27T08:45:00.1493711Z",
255+
type = "CustomerUpdated",
256+
customerId = 54617,
257+
resourceId = "54617",
258+
resourceURI = "https://external-service-one.testing.net/api/v1/customer/54617"
259+
});
260+
}
261+
262+
private static StringContent GetCustomerResponse()
263+
{
264+
return ContentHelper.CreateJsonStringContent(new
265+
{
266+
id = 54624,
267+
title = "Mr",
268+
firstName = "Peter",
269+
lastName = "Smith",
270+
dateOfBirth = "1970-04-25",
271+
languageCode = "en-GB",
272+
address = new
273+
{
274+
line1 = "8 High Street",
275+
line2 = (string)null,
276+
line3 = (string)null,
277+
town = "Luton",
278+
county = "Bedfordshire",
279+
postcode = "LT12 6TY",
280+
countryCode = "UK",
281+
countryName = "United Kingdom"
282+
}
283+
});
284+
}
285+
}
286+
}

src/LogicAppUnit.Samples.LogicApps.Tests/LogicAppUnit.Samples.LogicApps.Tests.csproj

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,15 @@
7777
<None Update="testConfiguration.json">
7878
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
7979
</None>
80+
<None Update="local.settings-custom.json">
81+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
82+
</None>
83+
<None Update="parameters-custom.json">
84+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
85+
</None>
86+
<None Update="connections-custom.json">
87+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
88+
</None>
8089
</ItemGroup>
8190

8291
</Project>

0 commit comments

Comments
 (0)