9.5 KiB
description, applyTo
| description | applyTo |
|---|---|
| Unit testing standards, test project setup, naming, build & run commands | **/*Tests.cs, **/*Test.cs, **/*.Tests.csproj |
Testing — Unit Tests, Build & Run
Every public method and class must have corresponding unit tests. Tests are not optional.
1. Test Framework & Project Setup
Recommended Stack
| Component | Package | Purpose |
|---|---|---|
| Test Framework | MSTest |
Test runner & assertions |
| Mocking | Moq |
Mock dependencies |
| UI Testing | Microsoft.Windows.Apps.Test |
WinUI UI automation (optional) |
Test Project Setup
Create a test project alongside the main project:
<SolutionRoot>/
<ProjectName>/ ← Main app project
<ProjectName>.Tests/ ← Unit test project
Test project .csproj should reference the main project:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Match TargetFramework to the main project's .csproj -->
<TargetFramework><!-- same as the main project's .csproj TargetFramework --></TargetFramework>
<UseWinUI>true</UseWinUI>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<!-- Use latest stable versions; do not hard-code version numbers in instructions -->
<PackageReference Include="MSTest.TestAdapter" Version="*" />
<PackageReference Include="MSTest.TestFramework" Version="*" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="*" />
<PackageReference Include="Moq" Version="*" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\<ProjectName>\<ProjectName>.csproj" />
</ItemGroup>
</Project>
2. Test Writing Rules
What to Test
- All public methods in ViewModels, Services, Helpers, and Models.
- Edge cases — null inputs, empty collections, boundary values.
- Error paths — exception handling, invalid state transitions.
- Business logic — calculations, transformations, state management.
What NOT to Test (Directly)
- XAML layout / visual rendering (use UI tests for that).
- Framework internals (e.g.,
InitializeComponent()). - Private methods — test them indirectly through public methods.
Test Naming Convention
Use the pattern: MethodName_Scenario_ExpectedResult
[TestMethod]
public void CalculateTotal_WithEmptyCart_ReturnsZero() { }
[TestMethod]
public void LoadDataAsync_WhenServiceThrows_SetsErrorState() { }
[TestMethod]
public async Task SaveAsync_WithValidInput_ReturnsTrue() { }
Test Structure (AAA Pattern)
Every test follows Arrange → Act → Assert:
[TestMethod]
public void Add_TwoPositiveNumbers_ReturnsSum()
{
// Arrange
var calculator = new Calculator();
// Act
int result = calculator.Add(2, 3);
// Assert
Assert.AreEqual(5, result);
}
ViewModel Testing Example
[TestMethod]
public async Task LoadItemsAsync_OnSuccess_PopulatesItems()
{
// Arrange
var mockService = new Mock<IDataService>();
mockService
.Setup(s => s.GetItemsAsync())
.ReturnsAsync(new List<Item> { new("Test") });
var viewModel = new MainViewModel(mockService.Object);
// Act
await viewModel.LoadItemsAsync();
// Assert
Assert.AreEqual(1, viewModel.Items.Count);
Assert.IsFalse(viewModel.IsLoading);
}
3. Test Organization
File Structure
Mirror the main project's folder structure (defined in winui-best-practices) in the test project. As the main project grows with subfolders under ViewModels/, Services/, Views/, etc., the test project must grow organically in the same way. This alignment enables on-demand test runs scoped to the area you changed:
<ProjectName>/ <ProjectName>.Tests/
Models/ Models/
User.cs UserTests.cs
ViewModels/ ViewModels/
MainViewModelTests.cs MainViewModelTests.cs
Settings/ Settings/
ThemeViewModel.cs ThemeViewModelTests.cs
Services/ Services/
DataService.cs DataServiceTests.cs
Auth/ Auth/
AuthService.cs AuthServiceTests.cs
Helpers/ Helpers/
StringHelper.cs StringHelperTests.cs
Converters/ Converters/
BoolToVisibilityConverter.cs BoolToVisibilityConverterTests.cs
One Test Class per Class Under Test
namespace <RootNamespace>.Tests.ViewModels;
[TestClass]
public class MainViewModelTests
{
// All tests for MainViewModel go here
}
Running Tests On-Demand
After a change, run only the tests related to the affected area instead of the full suite. Detect the platform first (matching the convention in .github/agents/Agents.md):
# Run from the test project folder
cd <ProjectName>.Tests
# Detect platform once per session (AMD64 -> x64; ARM64/x86 unchanged)
$arch = $env:PROCESSOR_ARCHITECTURE
$Platform = if ($arch -eq 'AMD64') { 'x64' } else { $arch }
# Run tests for a specific class
dotnet test -c Debug -p:Platform=$Platform --filter "FullyQualifiedName~MainViewModelTests"
# Run a single test
dotnet test -c Debug -p:Platform=$Platform --filter "FullyQualifiedName~MainViewModelTests.LoadItemsAsync_OnSuccess_PopulatesItems"
# Run all tests in a namespace (e.g., all ViewModel tests)
dotnet test -c Debug -p:Platform=$Platform --filter "FullyQualifiedName~Tests.ViewModels"
# Run tests in a subfolder namespace (e.g., only Settings ViewModels)
dotnet test -c Debug -p:Platform=$Platform --filter "FullyQualifiedName~Tests.ViewModels.Settings"
# Run the full suite (for cross-cutting changes)
dotnet test -c Debug -p:Platform=$Platform
---
## 4. Test-Specific Commands
For general build and register commands, see **Build, Run & Deploy** in `.github/agents/Agents.md`.
For on-demand test filtering, see **Running Tests On-Demand** above.
Below are additional test commands:
### Build Only the Test Project
```powershell
cd <ProjectName>.Tests
$arch = $env:PROCESSOR_ARCHITECTURE
$Platform = if ($arch -eq 'AMD64') { 'x64' } else { $arch }
dotnet build -c Debug -p:Platform=$Platform
Run Tests with Verbose Output
dotnet test -c Debug -p:Platform=$Platform --verbosity normal
5. Agent Workflow for Tests
When you write or modify code, follow this sequence:
- Implement the feature or fix in the main project.
- Write unit tests for every new/changed public method.
- Build — see Build, Run & Deploy in
.github/agents/Agents.md. Fix all errors and warnings. - Run tests —
dotnet test -c Debug -p:Platform=$Platform(from the test project folder; detect$Platformas shown above) and ensure all pass. - Review — Confirm tests cover the happy path, edge cases, and error cases.
When Modifying Existing Code
- Run existing tests first to establish a baseline.
- Make the code change.
- Run tests again — fix any failures.
- Add new tests if the change introduces new behaviour.
Coverage Goals
- Aim for 80%+ code coverage on business logic (ViewModels, Services).
- 100% coverage of utility/helper methods.
- UI code-behind is exempt from unit test coverage (tested via integration/UI tests).
6. Common Test Pitfalls
| Pitfall | Fix |
|---|---|
| Test depends on another test's state | Each test must be fully independent |
| Testing multiple things in one test | One assertion per logical concept |
| Tests pass but don't actually verify anything | Always have meaningful assertions |
| Mocking too much | Mock only external dependencies, not the class under test |
| Testing implementation details | Test behaviour and outcomes, not internal method calls |
Async tests without await |
Always await async methods and use async Task return type |
Validation
- Build & run tests — see Build, Run & Deploy in
.github/agents/Agents.md. - Verify all tests pass — zero failures, zero skipped without justification.
- Verify naming follows
MethodName_Scenario_ExpectedResultpattern. - Verify AAA structure (Arrange/Act/Assert) in every test method.
- Confirm coverage goals: 80%+ on ViewModels/Services, 100% on helpers.
Must Read & Research
Agent Rule: Before writing or modifying tests, you must fetch and review the relevant references below using
fetch_webpage. Apply what you learn — do not skip this step.
| # | Reference | When to consult |
|---|---|---|
| 1 | Unit testing C# with MSTest | Setting up test project, writing first tests, MSTest attributes |
| 2 | Unit testing best practices .NET | Every time you write tests — naming, structure, AAA pattern |
| 3 | Moq Quickstart | Mocking interfaces, setting up Returns/Throws, verifying calls |
| 4 | FluentAssertions Documentation | Writing expressive assertions (Should().Be(), collections, exceptions) |
| 5 | dotnet test CLI | Running tests from terminal, filtering, verbosity options |
| 6 | Test Explorer in Visual Studio | Debugging tests, viewing coverage, understanding test output |