Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Unit Tests

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
test:
runs-on: ubuntu-latest

permissions:
contents: read
actions: read
checks: write

steps:
- uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'

- name: Restore dependencies
run: dotnet restore http.sln

- name: Build
run: dotnet build http.sln --no-restore --configuration Release

- name: Run tests
run: dotnet test http.sln --configuration Release --verbosity normal --logger "trx;LogFileName=test-results.trx" --collect:"XPlat Code Coverage" --results-directory ./TestResults

- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results
path: TestResults/**/*.trx
retention-days: 30

- name: Upload coverage results
uses: actions/upload-artifact@v4
if: always()
with:
name: coverage-results
path: TestResults/**/coverage.cobertura.xml
retention-days: 30
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ bld/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
TestResults/

# NUNIT
*.VisualState.xml
Expand Down
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,38 @@ public IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "post")] Http
}
```

## Testing

This project includes comprehensive unit tests for the HTTP trigger functions.

### Running Unit Tests

From the repository root, run:

```shell
# Run all tests
dotnet test http.Tests/http.Tests.csproj

# Run with detailed output
dotnet test http.Tests/http.Tests.csproj --logger "console;verbosity=detailed"

# Run with code coverage
dotnet test http.Tests/http.Tests.csproj --collect:"XPlat Code Coverage"
```

### Test Coverage

The unit tests cover:
- **httpGetFunction**: Name parameter handling (valid, empty, null) and logging validation
- **HttpPostBody**: Valid person data, validation failures, and error handling
- **Total**: 10 tests covering primary functionality and error scenarios

See [test-specification.md](./test-specification.md) for detailed test documentation and [test-report.md](./test-report.md) for the latest test results.

### Continuous Integration

Tests are automatically run on every push and pull request via GitHub Actions. See [.github/workflows/test.yml](.github/workflows/test.yml) for the workflow configuration.

## Deploy to Azure

Run this command to provision the function app, with any required Azure resources, and deploy your code:
Expand Down
170 changes: 170 additions & 0 deletions http.Tests/HttpPostBodyTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
using Company.Function;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Moq;

namespace http.Tests
{
public class HttpPostBodyTests
{
private readonly Mock<ILogger<HttpPostBody>> _mockLogger;
private readonly Mock<ILoggerFactory> _mockLoggerFactory;
private readonly HttpPostBody _function;
private readonly Mock<HttpRequest> _mockRequest;

public HttpPostBodyTests()
{
_mockLogger = new Mock<ILogger<HttpPostBody>>();
_mockLoggerFactory = new Mock<ILoggerFactory>();
_mockLoggerFactory.Setup(x => x.CreateLogger(It.IsAny<string>()))
.Returns(_mockLogger.Object);
_function = new HttpPostBody(_mockLoggerFactory.Object);
_mockRequest = new Mock<HttpRequest>();
}

[Fact]
public void Run_WithValidPerson_ReturnsOkResultWithPersonalizedMessage()
{
// Arrange
var person = new Person("Jane", 30);
string expectedMessage = "Hello, Jane! You are 30 years old.";

// Act
var result = _function.Run(_mockRequest.Object, person);

// Assert
Assert.IsType<OkObjectResult>(result);
var okResult = result as OkObjectResult;
Assert.Equal(expectedMessage, okResult?.Value);

// Verify logging - should be 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.AtLeast(2));
}

[Fact]
public void Run_WithEmptyName_ReturnsBadRequest()
{
// Arrange
var person = new Person("", 25);
string expectedErrorMessage = "Please provide both name and age in the request body.";

// Act
var result = _function.Run(_mockRequest.Object, person);

// Assert
Assert.IsType<BadRequestObjectResult>(result);
var badRequestResult = result as BadRequestObjectResult;
Assert.Equal(expectedErrorMessage, badRequestResult?.Value);

// Verify logging
_mockLogger.Verify(
x => x.Log(
LogLevel.Information,
It.IsAny<EventId>(),
It.Is<It.IsAnyType>((v, t) => v.ToString()!.Contains("no name/age provided")),
null,
It.IsAny<Func<It.IsAnyType, Exception?, string>>()),
Times.Once);
}

[Fact]
public void Run_WithNullName_ReturnsBadRequest()
{
// Arrange
var person = new Person(null!, 25);
string expectedErrorMessage = "Please provide both name and age in the request body.";

// Act
var result = _function.Run(_mockRequest.Object, person);

// Assert
Assert.IsType<BadRequestObjectResult>(result);
var badRequestResult = result as BadRequestObjectResult;
Assert.Equal(expectedErrorMessage, badRequestResult?.Value);
}

[Fact]
public void Run_WithZeroAge_ReturnsBadRequest()
{
// Arrange
var person = new Person("John", 0);
string expectedErrorMessage = "Please provide both name and age in the request body.";

// Act
var result = _function.Run(_mockRequest.Object, person);

// Assert
Assert.IsType<BadRequestObjectResult>(result);
var badRequestResult = result as BadRequestObjectResult;
Assert.Equal(expectedErrorMessage, badRequestResult?.Value);
}

[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);
}

[Fact]
public void Run_WithInvalidPerson_LogsInformationMessage()
{
// Arrange
var person = new Person("", 0);

// Act
_function.Run(_mockRequest.Object, person);

// Assert
_mockLogger.Verify(
x => x.Log(
LogLevel.Information,
It.IsAny<EventId>(),
It.Is<It.IsAnyType>((v, t) => v.ToString()!.Contains("no name/age provided")),
null,
It.IsAny<Func<It.IsAnyType, Exception?, string>>()),
Times.Once);
}
}
}
28 changes: 28 additions & 0 deletions http.Tests/http.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.3.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
</ItemGroup>

<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\http\http.csproj" />
</ItemGroup>

</Project>
Loading