.NET Core 6.0单元测试:如何正确注册带构造依赖的服务?
单元测试获取服务失败问题解决
问题描述
运行获取天气列表的单元测试时,出现无法从服务提供程序中获取类型为WeatherServiceManager的服务,因为该服务未注册的报错。
相关代码
1. 服务注册类(ApplicationRegister)
public static class ApplicationService { /// <summary> /// Method to configure application level services with the di container /// </summary> /// <param name="services"></param> /// <param name="configuration"></param> public static void ConfigureApplicationServices(this IServiceCollection services) { services.AddScoped<IWeatherServiceManager, WeatherServiceManager>(); services.AddScoped<IHttpClientApiService, HttpClientApiService>(); } }
2. Program.cs 服务注册片段
using weather_api.ServiceRegister; var builder = WebApplication.CreateBuilder(args); // Add services to the container. #region [Register Services] builder.Services.ConfigureApplicationServices(); #endregion
3. WeatherServiceManager 实现类
public class WeatherServiceManager : IWeatherServiceManager { private readonly IHttpClientApiService httpClientApiService; private readonly IConfiguration configuration; public WeatherServiceManager(IHttpClientApiService httpClientApiService, IConfiguration configuration) { this.httpClientApiService = httpClientApiService; this.configuration = configuration; } }
4. 单元测试类
public class WeatherControllerTest { private readonly IWeatherServiceManager _weatherServiceManager; public WeatherControllerTest() { var services = new ServiceCollection(); var serviceProvider = services.BuildServiceProvider(); _weatherServiceManager = serviceProvider.GetRequiredService<WeatherServiceManager>(); } /// <summary> /// Get Weather details using zip code and city name /// </summary> /// <returns></returns> [Fact] public async Task GetWeatherInformation() { // Act & Assert var items = Assert.IsType<List<WeatherViewModel>>(await _weatherServiceManager.GetForecast("London")); Assert.Equal(3, items.Count); } }
问题原因及修复方案
核心原因
单元测试构造函数里只创建了空的ServiceCollection,完全没有注册任何服务,直接调用GetRequiredService必然报错。另外WeatherServiceManager还依赖IHttpClientApiService和IConfiguration,就算注册了自身,这两个依赖也缺失。
修复步骤
- 注册必要服务:在测试的服务集合中注册所需服务,可直接复用已有的
ConfigureApplicationServices扩展方法。 - 提供模拟配置:测试环境用内存配置模拟
IConfiguration。 - Mock外部依赖:对
IHttpClientApiService这类调用外部API的服务,用Mock对象替代真实实现,避免依赖外部服务。
修复后的单元测试示例(使用Moq实现Mock):
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Configuration; using Moq; public class WeatherControllerTest { private readonly IWeatherServiceManager _weatherServiceManager; public WeatherControllerTest() { var services = new ServiceCollection(); // 1. 添加模拟配置 var testConfig = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary<string, string> { {"WeatherApi:BaseUrl", "https://test-api.example.com"} }) .Build(); services.AddSingleton<IConfiguration>(testConfig); // 2. Mock IHttpClientApiService,返回测试用固定数据 var httpClientMock = new Mock<IHttpClientApiService>(); httpClientMock.Setup(x => x.GetAsync<List<WeatherViewModel>>(It.IsAny<string>())) .ReturnsAsync(new List<WeatherViewModel> { new(), new(), new() }); services.AddSingleton(httpClientMock.Object); // 3. 注册WeatherServiceManager services.AddScoped<IWeatherServiceManager, WeatherServiceManager>(); var serviceProvider = services.BuildServiceProvider(); _weatherServiceManager = serviceProvider.GetRequiredService<IWeatherServiceManager>(); } [Fact] public async Task GetWeatherInformation() { // Act var items = await _weatherServiceManager.GetForecast("London"); // Assert Assert.IsType<List<WeatherViewModel>>(items); Assert.Equal(3, items.Count); } }
额外提示
单元测试重点是验证WeatherServiceManager的业务逻辑,而非调用真实外部API,所以用Mock隔离外部依赖是标准做法。如果不想用Moq,也可以手动写一个IHttpClientApiService的测试实现类,返回固定测试数据。
内容的提问来源于stack exchange,提问作者Kamran Khan
相关产品推荐
相关产品推荐

