Add DI container and view-model tests

Add a composition-root test that builds the provider with ValidateOnBuild to catch missing registrations (without touching the real hardware monitor), plus per-service registration assertions. Add a MainWindowViewModel test that constructs the view model entirely from faked IHardwareMonitorService/ILogger/IRemoteWebServer, proving the testability win. To make that possible, inject the UI DispatcherQueue (captured on the UI thread by the composition factory) instead of calling the static DispatcherQueue.GetForCurrentThread() in the view-model constructor, which threw in the headless test host; the tree-rebuild marshalling now treats a null dispatcher as direct invocation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-06-02 15:20:40 -05:00
co-authored by Copilot
parent 3e71e09a25
commit dcb6a5b803
4 changed files with 98 additions and 5 deletions
@@ -0,0 +1,49 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
using System.Linq;
using LibreHardwareMonitor.Windows.WinUI.Composition;
using LibreHardwareMonitor.Windows.WinUI.Services;
using LibreHardwareMonitor.Windows.WinUI.Services.Tracing;
using LibreHardwareMonitor.Windows.WinUI.ViewModels;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
namespace LibreHardwareMonitor.Windows.WinUI.Tests.Composition;
// Guards the single composition root: every dependency referenced by a registered service must
// itself be registered, otherwise the app fails to resolve its graph at launch. ValidateOnBuild
// builds the dependency call sites without instantiating the services, so this stays off the real
// hardware-monitor / kernel-driver path while still catching missing registrations.
public class ServiceCollectionExtensionsTests
{
[Fact]
public void AddAppServices_BuildsProviderWithoutMissingRegistrations()
{
ServiceProvider provider = new ServiceCollection()
.AddAppServices(NoOpStartupTracer.Instance)
.BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true });
Assert.NotNull(provider);
provider.Dispose();
}
[Theory]
[InlineData(typeof(AppSettings))]
[InlineData(typeof(IStartupTracer))]
[InlineData(typeof(IHardwareMonitorService))]
[InlineData(typeof(ILogger))]
[InlineData(typeof(IRemoteWebServer))]
[InlineData(typeof(SensorSelectionService))]
[InlineData(typeof(PlotTrackingService))]
[InlineData(typeof(StartupService))]
[InlineData(typeof(MainWindowViewModel))]
[InlineData(typeof(IMainWindowRuntimeFactory))]
public void AddAppServices_RegistersExpectedService(System.Type serviceType)
{
IServiceCollection services = new ServiceCollection().AddAppServices(NoOpStartupTracer.Instance);
Assert.Contains(services, descriptor => descriptor.ServiceType == serviceType);
}
}
@@ -0,0 +1,41 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
using System;
using LibreHardwareMonitor.Windows.WinUI.Services;
using LibreHardwareMonitor.Windows.WinUI.Services.Tracing;
using LibreHardwareMonitor.Windows.WinUI.ViewModels;
using Moq;
using Xunit;
namespace LibreHardwareMonitor.Windows.WinUI.Tests.ViewModels;
// Demonstrates the headline win of the DI refactor: MainWindowViewModel can now be constructed with
// faked domain services (no real hardware monitor / kernel driver), and it reads its settings and
// wires the web-server root provider during construction.
public class MainWindowViewModelTests
{
[Fact]
public void Constructor_WithFakedServices_ReadsSettingsAndWiresWebServerRoot()
{
AppSettings settings = AppSettings.LoadDefault();
var hardwareMonitor = new Mock<IHardwareMonitorService>();
var logger = new Mock<ILogger>();
var webServer = new Mock<IRemoteWebServer>();
using var viewModel = new MainWindowViewModel(
settings,
hardwareMonitor.Object,
logger.Object,
new SensorSelectionService(settings),
webServer.Object,
new PlotTrackingService(),
new StartupService(),
NoOpStartupTracer.Instance);
Assert.True(viewModel.ShowValueColumn);
Assert.True(viewModel.ShowMaxColumn);
webServer.Verify(s => s.SetRootProvider(It.IsAny<Func<SensorTreeItemViewModel?>>()), Times.Once);
}
}