Skip to content
Open
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
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,10 @@ SampleWebApp/Properties/PublishProfiles/samplemvcwebapp - Web Deploy.pubxml
SampleWebApp/Properties/PublishProfiles/samplemvcwebapp.net - WebWiz.pubxml
git/.gitattributes
git/.gitignore

# Coverage reports
coverage.cobertura.xml
coverage.*.xml
TestResults/
# BenchmarkDotNet artifacts
BenchmarkDotNet.Artifacts/
34 changes: 34 additions & 0 deletions netcore/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# SampleWebApp — .NET 6 migration (NET-1 / NET-12)

This directory contains the **.NET 6** port of `SampleMvcWebApp` and its
testing / quality-assurance infrastructure. It exists alongside the original
MVC5 / .NET Framework 4.5.1 solution at the repository root so the two can
evolve independently during the phased migration.

## Projects

| Project | Purpose |
|--------------------------------------------------|----------------------------------------------------|
| `src/SampleWebApp` | Migrated ASP.NET Core 6 MVC web app |
| `tests/SampleWebApp.UnitTests` | xUnit unit tests (controller + model) |
| `tests/SampleWebApp.IntegrationTests` | HTTP-level endpoint tests via `TestServer` |
| `tests/SampleWebApp.PerformanceTests` | BenchmarkDotNet micro-benchmarks |

Solution file: `SampleWebApp.NetCore.sln`.

## Prerequisites

- [.NET 6 SDK](https://dotnet.microsoft.com/download/dotnet/6.0)

## Quick start

```bash
dotnet build netcore/SampleWebApp.NetCore.sln -c Release
dotnet test netcore/tests/SampleWebApp.UnitTests/SampleWebApp.UnitTests.csproj -c Release
dotnet test netcore/tests/SampleWebApp.IntegrationTests/SampleWebApp.IntegrationTests.csproj -c Release
dotnet run --project netcore/src/SampleWebApp -c Release
```

See [`docs/testing.md`](./docs/testing.md) for the full testing guide and
[`docs/test-results.md`](./docs/test-results.md) for the baseline results
captured when the framework was established.
50 changes: 50 additions & 0 deletions netcore/SampleWebApp.NetCore.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8E72D404-DD19-4603-A15D-4B8ABBFDB8E0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleWebApp", "src\SampleWebApp\SampleWebApp.csproj", "{4CEB62FD-E3A5-4634-8FF7-0049D9741648}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{240C38A8-CB71-4826-8DA1-39E233DCFB5F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleWebApp.UnitTests", "tests\SampleWebApp.UnitTests\SampleWebApp.UnitTests.csproj", "{CBB86116-247E-4345-87BD-2CF631C62B3C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleWebApp.IntegrationTests", "tests\SampleWebApp.IntegrationTests\SampleWebApp.IntegrationTests.csproj", "{A8FF4330-6BDD-4F90-A7B0-1135CA2B761A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleWebApp.PerformanceTests", "tests\SampleWebApp.PerformanceTests\SampleWebApp.PerformanceTests.csproj", "{EFDFEB9A-60EF-4C24-9E26-BC8B5FCC2870}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4CEB62FD-E3A5-4634-8FF7-0049D9741648}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4CEB62FD-E3A5-4634-8FF7-0049D9741648}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4CEB62FD-E3A5-4634-8FF7-0049D9741648}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4CEB62FD-E3A5-4634-8FF7-0049D9741648}.Release|Any CPU.Build.0 = Release|Any CPU
{CBB86116-247E-4345-87BD-2CF631C62B3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CBB86116-247E-4345-87BD-2CF631C62B3C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CBB86116-247E-4345-87BD-2CF631C62B3C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CBB86116-247E-4345-87BD-2CF631C62B3C}.Release|Any CPU.Build.0 = Release|Any CPU
{A8FF4330-6BDD-4F90-A7B0-1135CA2B761A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A8FF4330-6BDD-4F90-A7B0-1135CA2B761A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A8FF4330-6BDD-4F90-A7B0-1135CA2B761A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A8FF4330-6BDD-4F90-A7B0-1135CA2B761A}.Release|Any CPU.Build.0 = Release|Any CPU
{EFDFEB9A-60EF-4C24-9E26-BC8B5FCC2870}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EFDFEB9A-60EF-4C24-9E26-BC8B5FCC2870}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EFDFEB9A-60EF-4C24-9E26-BC8B5FCC2870}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EFDFEB9A-60EF-4C24-9E26-BC8B5FCC2870}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{4CEB62FD-E3A5-4634-8FF7-0049D9741648} = {8E72D404-DD19-4603-A15D-4B8ABBFDB8E0}
{CBB86116-247E-4345-87BD-2CF631C62B3C} = {240C38A8-CB71-4826-8DA1-39E233DCFB5F}
{A8FF4330-6BDD-4F90-A7B0-1135CA2B761A} = {240C38A8-CB71-4826-8DA1-39E233DCFB5F}
{EFDFEB9A-60EF-4C24-9E26-BC8B5FCC2870} = {240C38A8-CB71-4826-8DA1-39E233DCFB5F}
EndGlobalSection
EndGlobal
19 changes: 19 additions & 0 deletions netcore/coverlet.runsettings
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Coverage configuration for `dotnet test --collect:"XPlat Code Coverage"`.
Program.cs is the ASP.NET bootstrap (DI wiring) and cshtml Razor views are
compiled views; neither is meaningfully unit-testable, so both are excluded.
-->
<RunSettings>
<DataCollectionRunSettings>
<DataCollectors>
<DataCollector friendlyName="XPlat code coverage">
<Configuration>
<Format>cobertura</Format>
<Include>[SampleWebApp]*</Include>
<ExcludeByFile>**/Program.cs,**/*.cshtml,**/obj/**/*.cs</ExcludeByFile>
</Configuration>
</DataCollector>
</DataCollectors>
</DataCollectionRunSettings>
</RunSettings>
76 changes: 76 additions & 0 deletions netcore/docs/test-results.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Test Results — NET-12

Baseline results captured while setting up the testing framework for the
.NET 6 migration. Reproduce with the commands in [`testing.md`](./testing.md).

## Unit tests (`SampleWebApp.UnitTests`)

- **13 / 13 tests passing**
- Duration: ~1.2 s
- Framework: xUnit 2.4 on .NET 6.0

Breakdown:

| Suite | Tests |
|------------------------------------|-------|
| `HomeControllerTests` | 6 |
| `InternalsInfoTests` | 3 |
| `InternalsInfoProviderTests` | 4 |

## Coverage

Collected with Coverlet (msbuild integration), excluding `Program.cs` and
`*.cshtml` views per the project's `coverlet.runsettings`.

| Module | Line | Branch | Method |
|---------------|------|--------|--------|
| `SampleWebApp`| 100% | 100% | 100% |

Per-file highlights:

| File | Line rate |
|---------------------------------------|-----------|
| `Controllers/HomeController.cs` | 100% |
| `Models/InternalsInfo.cs` | 100% |

Exceeds the NET-12 acceptance criterion of **&gt;90% coverage** on
`HomeController`.

## Integration tests (`SampleWebApp.IntegrationTests`)

- **10 / 10 tests passing**
- Duration: ~1.5 s
- Host: `Microsoft.AspNetCore.Mvc.Testing` `WebApplicationFactory<Program>`

All `HomeController` endpoints (`/`, `/Home`, `/Home/Index`, `/Home/About`,
`/Home/Contact`, `/Home/Internals`, `/Home/CodeView`) return `200 OK` with
`Content-Type: text/html` and non-empty bodies. Content assertions cover the
About page description message and the Internals page runtime metrics block.
`/Home/DoesNotExist` correctly returns `404 Not Found`.

## Performance benchmarks (`SampleWebApp.PerformanceTests`)

The benchmark project ships with `HomeControllerBenchmarks` (one benchmark per
action method, `Index` as the `Baseline`) and `InternalsInfoBenchmarks`.
BenchmarkDotNet discovers all 6 benchmarks:

```
SampleWebApp.PerformanceTests.HomeControllerBenchmarks.Index
SampleWebApp.PerformanceTests.HomeControllerBenchmarks.About
SampleWebApp.PerformanceTests.HomeControllerBenchmarks.Contact
SampleWebApp.PerformanceTests.HomeControllerBenchmarks.Internals
SampleWebApp.PerformanceTests.HomeControllerBenchmarks.CodeView
SampleWebApp.PerformanceTests.InternalsInfoBenchmarks.CreateInternalsInfo
```

Full benchmark runs are intentionally not part of CI (they take minutes and
are sensitive to the host machine). They are intended as a nightly /
on-demand comparison harness: once the legacy MVC5 controller is retired,
the numbers produced here become the official baseline used when reviewing
future performance-sensitive PRs.

Sanity-check invocation (fast, just enumerates the benchmarks):

```bash
dotnet run --project netcore/tests/SampleWebApp.PerformanceTests -c Release -- --list flat
```
124 changes: 124 additions & 0 deletions netcore/docs/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Testing Framework & Quality Assurance (.NET 6 migration)

This document describes the testing framework put in place for the
`SampleWebApp` **.NET 6 migration** (see JIRA `NET-12`). The framework lives
entirely under `netcore/` and is independent of the legacy MVC5 `Tests` project
so both can coexist during the migration.

## Layout

```
netcore/
├── SampleWebApp.NetCore.sln
├── coverlet.runsettings
├── src/
│ └── SampleWebApp/ # Migrated ASP.NET Core 6 MVC app
├── tests/
│ ├── SampleWebApp.UnitTests/ # xUnit + Moq + FluentAssertions
│ ├── SampleWebApp.IntegrationTests/ # WebApplicationFactory<Program>
│ └── SampleWebApp.PerformanceTests/ # BenchmarkDotNet benchmarks
└── docs/
└── testing.md # This file
```

## Stack

| Concern | Tool |
|-----------------------|---------------------------------------------------------|
| Test runner | [xUnit](https://xunit.net/) 2.4 |
| Mocking | [Moq](https://github.com/moq/moq) 4.18 |
| Assertions | [FluentAssertions](https://fluentassertions.com/) 6.12 |
| Web host for tests | `Microsoft.AspNetCore.Mvc.Testing` 6.0 |
| Code coverage | [Coverlet](https://github.com/coverlet-coverage/coverlet) 3.1 |
| Performance / bench | [BenchmarkDotNet](https://benchmarkdotnet.org/) 0.13 |

## Running the tests

From the repository root:

```bash
# Restore & build everything
dotnet build netcore/SampleWebApp.NetCore.sln -c Release

# Run unit tests with coverage (Cobertura report at tests/SampleWebApp.UnitTests/coverage.cobertura.xml)
dotnet test netcore/tests/SampleWebApp.UnitTests/SampleWebApp.UnitTests.csproj \
-c Release \
/p:CollectCoverage=true \
/p:CoverletOutputFormat=cobertura \
'/p:Include="[SampleWebApp]*"' \
'/p:ExcludeByFile="**/Program.cs,**/*.cshtml,**/obj/**/*.cs"'

# Run integration tests (boots the app in-process)
dotnet test netcore/tests/SampleWebApp.IntegrationTests/SampleWebApp.IntegrationTests.csproj -c Release

# Run performance benchmarks
dotnet run --project netcore/tests/SampleWebApp.PerformanceTests -c Release
```

Alternatively, use the runsettings file for `dotnet test --collect:"XPlat Code Coverage"`:

```bash
dotnet test netcore/SampleWebApp.NetCore.sln \
--collect:"XPlat Code Coverage" \
--settings netcore/coverlet.runsettings
```

## What is covered

### Unit tests — `SampleWebApp.UnitTests`

`HomeControllerTests`:

- Constructor argument validation (null provider throws)
- `Index`, `Contact`, `CodeView` return the default `ViewResult`
- `About` sets `ViewBag.Message` to the expected string
- `Internals` resolves an `InternalsInfo` model from the injected
`IInternalsInfoProvider` and surfaces every metric onto the view

`InternalsInfoTests` / `InternalsInfoProviderTests`:

- Constructor argument validation
- All four counter properties are populated from the provider
- The default (parameterless) constructor exercises the real provider
- Each concrete provider method returns sane, runtime-independent values

### Integration tests — `SampleWebApp.IntegrationTests`

`HomeControllerEndpointTests` uses `WebApplicationFactory<Program>` to boot
the real app in-process and hits every endpoint over HTTP:

- `GET /`, `/Home`, `/Home/Index`, `/Home/About`, `/Home/Contact`,
`/Home/Internals`, `/Home/CodeView` — each returns `200 OK` and `text/html`
- `GET /Home/About` contains the description message rendered from the view
- `GET /Home/Internals` contains all four runtime-metric labels
- `GET /Home/DoesNotExist` returns `404 Not Found`

### Performance benchmarks — `SampleWebApp.PerformanceTests`

`HomeControllerBenchmarks` measures every action method in isolation
(`Index` as the `Baseline`). `InternalsInfoBenchmarks` measures the per-call
cost of assembling an `InternalsInfo` snapshot. Benchmarks run with
`[MemoryDiagnoser]` enabled so allocations are reported alongside timings.

## Coverage expectations

The acceptance criteria for `NET-12` require **&gt;90% coverage on
`HomeController`**. The current suite achieves **100% line, branch, and method
coverage** on both `Controllers/HomeController.cs` and `Models/InternalsInfo.cs`.

`Program.cs` (the ASP.NET Core bootstrap / DI wiring) and all Razor
`.cshtml` views are excluded from coverage measurement since they are exercised
by the integration tests rather than unit tests.

## CI integration

The `dotnet test` commands above emit a `coverage.cobertura.xml` report that
can be consumed by any CI coverage tool (Codecov, Coveralls, Azure Pipelines,
ReportGenerator, etc.). A suggested CI pipeline looks like:

1. `dotnet restore netcore/SampleWebApp.NetCore.sln`
2. `dotnet build netcore/SampleWebApp.NetCore.sln -c Release --no-restore`
3. `dotnet test` for the unit + integration projects with coverage collection
4. Fail the build if the `HomeController` line-rate drops below 0.9
5. (Optional) run the benchmark project as a nightly job and archive the
BenchmarkDotNet artifacts to track perf regressions across commits.
33 changes: 33 additions & 0 deletions netcore/src/SampleWebApp/Controllers/HomeController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Microsoft.AspNetCore.Mvc;
using SampleWebApp.Models;

namespace SampleWebApp.Controllers;

/// <summary>
/// .NET 6 migration of the original MVC5 <c>HomeController</c>. Action
/// signatures and view names are preserved so existing routes continue to work.
/// </summary>
public class HomeController : Controller
{
private readonly IInternalsInfoProvider _internalsInfoProvider;

public HomeController(IInternalsInfoProvider internalsInfoProvider)
{
_internalsInfoProvider = internalsInfoProvider
?? throw new ArgumentNullException(nameof(internalsInfoProvider));
}

public IActionResult Index() => View();

public IActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}

public IActionResult Contact() => View();

public IActionResult Internals() => View(new InternalsInfo(_internalsInfoProvider));

public IActionResult CodeView() => View();
}
Loading