--- description: 'Unit testing standards, test project setup, naming, build & run commands' applyTo: '**/*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: ``` / / ← Main app project .Tests/ ← Unit test project ``` Test project `.csproj` should reference the main project: ```xml true enable false ``` --- ## 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` ```csharp [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**: ```csharp [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 ```csharp [TestMethod] public async Task LoadItemsAsync_OnSuccess_PopulatesItems() { // Arrange var mockService = new Mock(); mockService .Setup(s => s.GetItemsAsync()) .ReturnsAsync(new List { 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](winui-best-practices.instructions.md)) 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: ``` / .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 ```csharp namespace .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`): ```powershell # Run from the test project folder cd .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 .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 ```powershell dotnet test -c Debug -p:Platform=$Platform --verbosity normal ``` --- ## 5. Agent Workflow for Tests When you write or modify code, follow this sequence: 1. **Implement the feature or fix** in the main project. 2. **Write unit tests** for every new/changed public method. 3. **Build** — see **Build, Run & Deploy** in `.github/agents/Agents.md`. Fix all errors and warnings. 4. **Run tests** — `dotnet test -c Debug -p:Platform=$Platform` (from the test project folder; detect `$Platform` as shown above) and ensure all pass. 5. **Review** — Confirm tests cover the happy path, edge cases, and error cases. ### When Modifying Existing Code 1. **Run existing tests first** to establish a baseline. 2. Make the code change. 3. **Run tests again** — fix any failures. 4. **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_ExpectedResult` pattern. - 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](https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-with-mstest) | Setting up test project, writing first tests, MSTest attributes | | 2 | [Unit testing best practices .NET](https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practices) | Every time you write tests — naming, structure, AAA pattern | | 3 | [Moq Quickstart](https://github.com/devlooped/moq/wiki/Quickstart) | Mocking interfaces, setting up `Returns`/`Throws`, verifying calls | | 4 | [FluentAssertions Documentation](https://fluentassertions.com/introduction) | Writing expressive assertions (`Should().Be()`, collections, exceptions) | | 5 | [dotnet test CLI](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-test) | Running tests from terminal, filtering, verbosity options | | 6 | [Test Explorer in Visual Studio](https://learn.microsoft.com/en-us/visualstudio/test/run-unit-tests-with-test-explorer) | Debugging tests, viewing coverage, understanding test output |