Skip to content

Commit c382577

Browse files
committed
adding a readme
1 parent 6342c08 commit c382577

File tree

1 file changed

+205
-0
lines changed

1 file changed

+205
-0
lines changed

test/Cli/Func.E2ETests/README.md

+205
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
# Azure Functions Core Tools E2E Testing Guide
2+
3+
## Overview
4+
5+
This project contains the E2E tests for Azure Functions Core Tools. When adding a new E2E test, please follow these guidelines to ensure consistency and efficient test execution.
6+
7+
### Test Organization
8+
9+
- Create tests within `Commands/Func[COMMAND_NAME]` directories
10+
- Separate tests into categories by organizing them in files with similar tests
11+
- Examples: `AuthTests`, `LogLevelTests`, etc.
12+
- This organization allows tests in different files to run in parallel, improving execution speed
13+
14+
### Test Types for `func start`
15+
16+
There are two main types of tests for the `func start` command:
17+
18+
#### 1. Tests with Fixtures
19+
20+
- Based on `BaseFunctionAppFixture`
21+
- The fixture handles initialization (`func init` and `func new`)
22+
- Best for tests that require a simple `HttpTrigger` with no additional configuration
23+
- Add your test to the appropriate fixture to avoid setup overhead
24+
25+
#### 2. Tests without Fixtures
26+
27+
- Based on `BaseE2ETests`
28+
- Sets up variables like `WorkingDirectory` and `FuncPath`
29+
- You must handle application setup in each test
30+
- Use the provided helper methods `FuncInitWithRetryAsync` and `FuncNewWithRetryAsync` for setup
31+
32+
### Important Notes
33+
34+
### Test Parallelization
35+
36+
**Dotnet isolated templates and dotnet in-proc templates CANNOT be run in parallel**
37+
38+
- Use traits to distinguish between dotnet-isolated and dotnet-inproc tests
39+
- This ensures they run sequentially rather than in parallel
40+
41+
### Node.js Tests
42+
43+
⚠️ **Node.js tests require explicit environment variable configuration**
44+
45+
- Due to flakiness in `func init` and `func new` not properly initializing environment variables
46+
- Always append `.WithEnvironmentVariable("FUNCTIONS_WORKER_RUNTIME", "node")` to the `funcStartCommand` like this:
47+
48+
```csharp
49+
var result = funcStartCommand
50+
.WithWorkingDirectory(WorkingDirectory)
51+
.WithEnvironmentVariable("FUNCTIONS_WORKER_RUNTIME", "node")
52+
.Execute(commandArgs.ToArray());
53+
```
54+
55+
## Step-by-Step Guide: Creating a `func start` Test without a Fixture
56+
57+
### 1. Get an Available Port
58+
59+
First, get an available port using `ProcessHelper.GetAvailablePort()` to ensure your test doesn't conflict with other processes:
60+
61+
```csharp
62+
int port = ProcessHelper.GetAvailablePort();
63+
```
64+
65+
### 2. Define Test Names
66+
67+
Create descriptive names for your test. For parameterized tests, include the parameters in the test name for better traceability:
68+
69+
```csharp
70+
string methodName = "Start_DotnetIsolated_Test_EnableAuthFeature";
71+
string uniqueTestName = $"{methodName}_{parameterValue1}_{parameterValue2}";
72+
```
73+
74+
### 3. Initialize a Function App
75+
76+
Use `FuncInitWithRetryAsync` to create a new function app with the appropriate runtime:
77+
78+
```csharp
79+
await FuncInitWithRetryAsync(uniqueTestName, new[] { ".", "--worker-runtime", "dotnet-isolated" });
80+
```
81+
82+
### 4. Add a Trigger Function
83+
84+
Use `FuncNewWithRetryAsync` to add a trigger function with specific configuration:
85+
86+
```csharp
87+
await FuncNewWithRetryAsync(uniqueTestName, new[] {
88+
".",
89+
"--template", "HttpTrigger",
90+
"--name", "HttpTrigger",
91+
"--authlevel", authLevel
92+
});
93+
```
94+
95+
### 5. Create a FuncStartCommand
96+
97+
Initialize a `FuncStartCommand` with the path to the func executable, test name, and logger:
98+
99+
```csharp
100+
var funcStartCommand = new FuncStartCommand(FuncPath, methodName, Log);
101+
```
102+
103+
### 6. Add a Process Started Handler (Optional)
104+
105+
If you need to wait for the host to start or check logs from a different process, add a `ProcessStartedHandler`:
106+
107+
```csharp
108+
funcStartCommand.ProcessStartedHandler = async (process) =>
109+
{
110+
await ProcessHelper.ProcessStartedHandlerHelper(port, process, funcStartCommand.FileWriter, "HttpTrigger");
111+
};
112+
```
113+
114+
The `ProcessStartedHandlerHelper` method:
115+
- Waits for the host to start
116+
- Makes an HTTP request to the function
117+
- Captures logs from the process
118+
- Returns when the function has processed the request
119+
120+
### 7. Build Command Arguments
121+
122+
Build your command arguments based on test parameters:
123+
124+
```csharp
125+
var commandArgs = new List<string> { "start", "--verbose", "--port", port.ToString() };
126+
if (enableSomeFeature)
127+
{
128+
commandArgs.Add("--featureFlag");
129+
}
130+
```
131+
132+
### 8. Execute the Command
133+
134+
Execute the command with the working directory and arguments:
135+
136+
```csharp
137+
var result = funcStartCommand
138+
.WithWorkingDirectory(WorkingDirectory)
139+
.Execute(commandArgs.ToArray());
140+
```
141+
142+
### 9. Validate the Results
143+
144+
Validate the command output contains the expected results based on test parameters:
145+
146+
```csharp
147+
if (someCondition)
148+
{
149+
result.Should().HaveStdOutContaining("expected output 1");
150+
}
151+
else
152+
{
153+
result.Should().HaveStdOutContaining("expected output 2");
154+
}
155+
```
156+
157+
## Testing with Fixtures
158+
159+
The steps are similar for creating a `func start` test with a fixture, except you can skip the setup logic that calls `func init` and `func new` as the fixture handles this for you.
160+
161+
## Complete Example
162+
163+
```csharp
164+
public async Task Start_DotnetIsolated_Test_EnableAuthFeature(
165+
string authLevel,
166+
bool enableAuth,
167+
string expectedResult)
168+
{
169+
int port = ProcessHelper.GetAvailablePort();
170+
string methodName = "Start_DotnetIsolated_Test_EnableAuthFeature";
171+
string uniqueTestName = $"{methodName}_{authLevel}_{enableAuth}";
172+
173+
// Setup the function app
174+
await FuncInitWithRetryAsync(uniqueTestName, new[] { ".", "--worker-runtime", "dotnet-isolated" });
175+
await FuncNewWithRetryAsync(uniqueTestName, new[] { ".", "--template", "HttpTrigger", "--name", "HttpTrigger", "--authlevel", authLevel });
176+
177+
// Create and configure the start command
178+
var funcStartCommand = new FuncStartCommand(FuncPath, methodName, Log);
179+
funcStartCommand.ProcessStartedHandler = async (process) =>
180+
{
181+
await ProcessHelper.ProcessStartedHandlerHelper(port, process, funcStartCommand.FileWriter, "HttpTrigger");
182+
};
183+
184+
// Build command arguments
185+
var commandArgs = new List<string> { "start", "--verbose", "--port", port.ToString() };
186+
if (enableAuth)
187+
{
188+
commandArgs.Add("--enableAuth");
189+
}
190+
191+
// Execute and validate
192+
var result = funcStartCommand
193+
.WithWorkingDirectory(WorkingDirectory)
194+
.Execute(commandArgs.ToArray());
195+
196+
if (string.IsNullOrEmpty(expectedResult))
197+
{
198+
result.Should().HaveStdOutContaining("\"status\": \"401\"");
199+
}
200+
else
201+
{
202+
result.Should().HaveStdOutContaining("Selected out-of-process host.");
203+
}
204+
}
205+
```

0 commit comments

Comments
 (0)