-
Notifications
You must be signed in to change notification settings - Fork 27
Add unit tests for HTTP trigger functions #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Co-authored-by: paulyuk <1968137+paulyuk@users.noreply.github.com>
|
@copilot did you run and validate these unit tests? Can i see a report? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot Please address my comments.
| ``` | ||
|
|
||
| ### Expected Outcomes | ||
| - All tests should pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I expected this PR to actually run and validate the tests, create a report, and ensure it matches expectations.
| 2. **Add Tests for New Features**: Any new functionality requires corresponding tests | ||
| 3. **Refactor Tests**: When tests become complex, refactor to improve readability | ||
| 4. **Review Coverage**: Regularly check code coverage and add tests for uncovered paths | ||
| 5. **Document Complex Tests**: Add comments for non-obvious test scenarios |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per my last comment as a part of maintenance I want to run the tests. Should these be connected as a CI test Action in this repo?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds comprehensive unit test coverage for two Azure Functions HTTP triggers (httpGetFunction and HttpPostBody) along with a detailed test specification document. The implementation follows XUnit and ASP.NET Core testing best practices, using Moq for dependency injection mocking and the Arrange-Act-Assert pattern throughout.
Key Changes:
- Implements 10 unit tests covering both success and error scenarios for HTTP GET and POST functions
- Creates a detailed test specification document aligned with ASP.NET Core unit testing guidance
- Tests properly mock
ILoggerdependencies and verify both functional behavior and logging
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| test-specification.md | Comprehensive test specification documenting methodology, test cases, suggested additional tests, and best practices alignment |
| http.sln | Adds test project to solution with Debug/Release configurations across Any CPU, x64, and x86 platforms |
| http.Tests/http.Tests.csproj | Test project configuration with XUnit, Moq, and code coverage packages targeting .NET 8.0 |
| http.Tests/httpGetFunctionTests.cs | Four unit tests covering name parameter handling (valid, empty, null) and logging verification |
| http.Tests/HttpPostBodyTests.cs | Six unit tests covering valid person data, validation failures (empty/null name, zero age), and logging |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="coverlet.collector" Version="6.0.4" /> | ||
| <PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.3.0" /> |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The version 2.3.0 of Microsoft.AspNetCore.Mvc.Core is quite old (released in 2018) and doesn't align with the .NET 8.0 target framework. For .NET 8.0 projects, consider using Microsoft.AspNetCore.Mvc.Core version 2.2.5 or relying on the framework reference already included in the main project. The main http.csproj includes <FrameworkReference Include="Microsoft.AspNetCore.App" /> which should provide the necessary types without requiring this separate package reference.
| <PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.3.0" /> |
| [Fact] | ||
| public void Run_LogsInformationMessage() | ||
| { | ||
| // Arrange | ||
| string name = "TestUser"; | ||
|
|
||
| // Act | ||
| _function.Run(_mockRequest.Object, name); | ||
|
|
||
| // Assert | ||
| _mockLogger.Verify( | ||
| x => x.Log( | ||
| LogLevel.Information, | ||
| It.IsAny<EventId>(), | ||
| It.Is<It.IsAnyType>((v, t) => v.ToString()!.Contains("C# HTTP trigger function processed a request")), | ||
| null, | ||
| It.IsAny<Func<It.IsAnyType, Exception?, string>>()), | ||
| Times.Once); | ||
| } |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test name Run_LogsInformationMessage is redundant with the other tests, which already verify logging behavior. This test doesn't add unique coverage since tests on lines 27-102 already verify that logging occurs with the appropriate messages. Consider removing this test or making it verify a specific logging scenario not covered by other tests (e.g., verify the exact log message format including "for Hello, TestUser.").
| [Fact] | ||
| public void Run_WithValidPerson_LogsMultipleInformationMessages() | ||
| { | ||
| // Arrange | ||
| var person = new Person("Alice", 28); | ||
|
|
||
| // Act | ||
| _function.Run(_mockRequest.Object, person); | ||
|
|
||
| // Assert - Verify logger was called multiple times | ||
| _mockLogger.Verify( | ||
| x => x.Log( | ||
| LogLevel.Information, | ||
| It.IsAny<EventId>(), | ||
| It.IsAny<It.IsAnyType>(), | ||
| null, | ||
| It.IsAny<Func<It.IsAnyType, Exception?, string>>()), | ||
| Times.Exactly(2)); | ||
|
|
||
| // Verify first log contains URL info | ||
| _mockLogger.Verify( | ||
| x => x.Log( | ||
| LogLevel.Information, | ||
| It.IsAny<EventId>(), | ||
| It.Is<It.IsAnyType>((v, t) => v.ToString()!.Contains("processed a request for url")), | ||
| null, | ||
| It.IsAny<Func<It.IsAnyType, Exception?, string>>()), | ||
| Times.Once); | ||
|
|
||
| // Verify second log contains person info | ||
| _mockLogger.Verify( | ||
| x => x.Log( | ||
| LogLevel.Information, | ||
| It.IsAny<EventId>(), | ||
| It.Is<It.IsAnyType>((v, t) => v.ToString()!.Contains("Alice") && v.ToString()!.Contains("28")), | ||
| null, | ||
| It.IsAny<Func<It.IsAnyType, Exception?, string>>()), | ||
| Times.Once); | ||
| } |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tests Run_WithValidPerson_LogsMultipleInformationMessages and Run_WithValidPerson_ReturnsOkResultWithPersonalizedMessage are testing the same scenario (valid person). The logging verification in lines 111-148 duplicates the logging verification already done in lines 42-49. Consider consolidating these tests or removing the separate logging-focused test to reduce redundancy.
| string expectedMessage = "Hello, World."; | ||
|
|
||
| // Act | ||
| var result = _function.Run(_mockRequest.Object, name!); |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The double null-forgiving operator usage (name! on line 86) is inconsistent with how null is handled in the actual function. The function signature accepts string name (non-nullable), but the implementation safely handles null values via string.IsNullOrEmpty(). Consider either changing the test to pass an empty string instead of null, or updating the actual function signature to string? name to better reflect the actual behavior being tested.
| var result = _function.Run(_mockRequest.Object, name!); | |
| var result = _function.Run(_mockRequest.Object, name); |
| [Fact] | ||
| public void Run_WithNullName_ReturnsBadRequest() | ||
| { |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The null-forgiving operator null! is used to construct a Person with a null name. The Person record expects a non-nullable string but the actual function handles null values. This inconsistency between the record definition and runtime behavior should be documented in the test specification or the Person record should be updated to accept string? Name to match the actual validation logic.
| [Fact] | |
| public void Run_WithNullName_ReturnsBadRequest() | |
| { | |
| // NOTE: This test intentionally passes a null value for the Person's Name property, | |
| // even though the Person record expects a non-nullable string. This highlights an | |
| // inconsistency between the record definition and the actual validation logic. | |
| // Ideally, the Person record should be updated to accept `string? Name` to match | |
| // the function's handling of null values. | |
| [Fact] | |
| public void Run_WithNullName_ReturnsBadRequest() |
| Debug|x64 = Debug|x64 | ||
| Debug|x86 = Debug|x86 | ||
| Release|Any CPU = Release|Any CPU | ||
| Release|x64 = Release|x64 | ||
| Release|x86 = Release|x86 | ||
| EndGlobalSection | ||
| GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
| {32C6DAE7-2329-47AB-8551-2A9EF0353C9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
| {32C6DAE7-2329-47AB-8551-2A9EF0353C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
| {32C6DAE7-2329-47AB-8551-2A9EF0353C9C}.Debug|x64.ActiveCfg = Debug|Any CPU | ||
| {32C6DAE7-2329-47AB-8551-2A9EF0353C9C}.Debug|x64.Build.0 = Debug|Any CPU | ||
| {32C6DAE7-2329-47AB-8551-2A9EF0353C9C}.Debug|x86.ActiveCfg = Debug|Any CPU | ||
| {32C6DAE7-2329-47AB-8551-2A9EF0353C9C}.Debug|x86.Build.0 = Debug|Any CPU | ||
| {32C6DAE7-2329-47AB-8551-2A9EF0353C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
| {32C6DAE7-2329-47AB-8551-2A9EF0353C9C}.Release|Any CPU.Build.0 = Release|Any CPU | ||
| {32C6DAE7-2329-47AB-8551-2A9EF0353C9C}.Release|x64.ActiveCfg = Release|Any CPU | ||
| {32C6DAE7-2329-47AB-8551-2A9EF0353C9C}.Release|x64.Build.0 = Release|Any CPU | ||
| {32C6DAE7-2329-47AB-8551-2A9EF0353C9C}.Release|x86.ActiveCfg = Release|Any CPU | ||
| {32C6DAE7-2329-47AB-8551-2A9EF0353C9C}.Release|x86.Build.0 = Release|Any CPU | ||
| {9A5C9145-5589-4C46-9418-9D0E3006F79F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
| {9A5C9145-5589-4C46-9418-9D0E3006F79F}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
| {9A5C9145-5589-4C46-9418-9D0E3006F79F}.Debug|x64.ActiveCfg = Debug|Any CPU | ||
| {9A5C9145-5589-4C46-9418-9D0E3006F79F}.Debug|x64.Build.0 = Debug|Any CPU | ||
| {9A5C9145-5589-4C46-9418-9D0E3006F79F}.Debug|x86.ActiveCfg = Debug|Any CPU | ||
| {9A5C9145-5589-4C46-9418-9D0E3006F79F}.Debug|x86.Build.0 = Debug|Any CPU | ||
| {9A5C9145-5589-4C46-9418-9D0E3006F79F}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
| {9A5C9145-5589-4C46-9418-9D0E3006F79F}.Release|Any CPU.Build.0 = Release|Any CPU | ||
| {9A5C9145-5589-4C46-9418-9D0E3006F79F}.Release|x64.ActiveCfg = Release|Any CPU | ||
| {9A5C9145-5589-4C46-9418-9D0E3006F79F}.Release|x64.Build.0 = Release|Any CPU | ||
| {9A5C9145-5589-4C46-9418-9D0E3006F79F}.Release|x86.ActiveCfg = Release|Any CPU | ||
| {9A5C9145-5589-4C46-9418-9D0E3006F79F}.Release|x86.Build.0 = Release|Any CPU |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The addition of x64 and x86 platform configurations is inconsistent with the original solution which only had "Any CPU". Unless there's a specific requirement for platform-specific builds, consider keeping only "Any CPU" configuration to maintain simplicity. If these platforms are needed, ensure they're documented in the PR description or project documentation.
| - Test thread safety (concurrent calls) | ||
|
|
||
| 2. **httpPostBodyFunction**: | ||
| - Test with negative age value |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The suggested test for "negative age value" is valuable, but the current validation logic in HttpPostBody doesn't explicitly handle negative ages - it only checks for person.Age == 0. Consider adding this to the primary test cases rather than just suggested additional tests, as negative ages represent invalid data that should be rejected. The validation logic may need to be updated to person.Age <= 0 to properly handle this edge case.
|
|
||
| ## Notes | ||
|
|
||
| - The current implementation uses bitwise OR (`|`) instead of logical OR (`||`) in the validation condition. While this works, it's not idiomatic. Tests will verify current behavior, but this could be noted as a code quality improvement. |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test specification correctly identifies that the validation logic in HttpPostBody uses bitwise OR (|) instead of logical OR (||). However, there's a more significant issue: the condition string.IsNullOrEmpty(person.Age.ToString()) will never be true because int.ToString() always returns a non-empty string (e.g., "0" for zero). The check for person.Age == 0 handles the zero case, but this condition is redundant and misleading. Consider documenting this in the test specification or noting it as a code quality issue that tests are currently verifying.
| - The current implementation uses bitwise OR (`|`) instead of logical OR (`||`) in the validation condition. While this works, it's not idiomatic. Tests will verify current behavior, but this could be noted as a code quality improvement. | |
| - The current implementation uses bitwise OR (`|`) instead of logical OR (`||`) in the validation condition. While this works, it's not idiomatic. Tests will verify current behavior, but this could be noted as a code quality improvement. | |
| - The validation logic also includes a redundant check: `string.IsNullOrEmpty(person.Age.ToString())`. Since `int.ToString()` never returns null or an empty string, this condition will always be false and is misleading. The check for `person.Age == 0` already handles the zero case. This should be documented as a code quality issue that tests are currently verifying. |
- Cherry-picked unit tests from PR #21 - Ran all 10 tests successfully (100% pass rate) - Created comprehensive test-report.md with detailed results - Added GitHub Actions workflow for automatic test execution - Updated .gitignore to exclude test artifacts Co-authored-by: paulyuk <1968137+paulyuk@users.noreply.github.com>
Purpose
Add XUnit test coverage for
httpGetFunctionandhttpPostBodyFunctionwith test specification document for team review.test-specification.md) following ASP.NET Core testing guidanceDoes this introduce a breaking change?
Pull Request Type
How to Test
What to Check
Verify that the following are valid
Other Information
Test Coverage:
httpGetFunction: Name parameter handling (valid, empty, null) and loggingHttpPostBody: Valid person data, validation failures (empty/null name, zero age), and loggingTest Specification includes:
The test specification document is ready for review by @fabiocav and team before potential iteration.
Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
learn.microsoft.com/home/REDACTED/work/_temp/ghcca-node/node/bin/node /home/REDACTED/work/_temp/ghcca-node/node/bin/node --enable-source-maps /home/REDACTED/work/_temp/copilot-developer-action-main/dist/index.js(dns block)If you need me to access, download, or install something from one of these locations, you can either:
Original prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.