单元测试时遭遇System.NullReferenceException问题求助
排查Minimal API单元测试空引用异常问题
问题根源
你的ParticipantsGetAll类中,私有字段participantService仅在AddRoute方法定义的路由委托被实际API请求触发时,才会从DI容器获取实例赋值。但单元测试中你直接实例化ParticipantsGetAll后调用HandleAsync,这个字段从未被赋值,始终为null,执行participantService!.ListAllAsync(query)时自然触发NullReferenceException。
解决方案
方案1:重构为构造函数注入(推荐)
将依赖通过构造函数注入,让类的依赖关系更清晰,同时方便测试时传入Mock实例。
修改ParticipantsGetAll代码:
public class ParticipantsGetAll : IEndpoint<IResult, GetAllParticipantsRequest> { const string uri = "api/participants-all"; private readonly ParticipantService _participantService; // 构造函数注入依赖 public ParticipantsGetAll(ParticipantService participantService) { _participantService = participantService; } public void AddRoute(IEndpointRouteBuilder app) { app.MapPost(uri, async ([FromBody] GetAllParticipantsRequest query) => { return await HandleAsync(query); }) .Produces<List<ParticipantSummaryModel>>() .WithTags("Participants") .WithName("GetAllParticipants"); } public async Task<IResult> HandleAsync(GetAllParticipantsRequest query) { var participants = await _participantService.ListAllAsync(query); return Results.Ok(participants); } }
修改测试代码(用Moq创建Mock服务):
[Fact] public async Task ListAllAsyncShouldReturn200Status() // 替换async void为async Task,xunit推荐写法 { // 准备测试数据 var query = new GetAllParticipantsRequest() { Name= "", KeyCordinator="", RelatedConnection="", Statuses = null }; var mockParticipants = new List<ParticipantSummaryModel> { ParticipantMockData.NewParticipantModel() }; // Mock ParticipantService var mockParticipantService = new Mock<ParticipantService>(); mockParticipantService.Setup(s => s.ListAllAsync(query)) .ReturnsAsync(mockParticipants); // 实例化被测类,传入Mock服务 var sut = new ParticipantsGetAll(mockParticipantService.Object); // 执行测试 var result = await sut.HandleAsync(query); // 断言 var okResult = Assert.IsType<OkObjectResult>(result); okResult.StatusCode.Should().Be(200); var returnedParticipants = Assert.IsType<List<ParticipantSummaryModel>>(okResult.Value); returnedParticipants.Should().BeEquivalentTo(mockParticipants); }
方案2:测试完整API端点(贴近真实请求场景)
如果想验证整个路由流程,可使用WebApplicationFactory搭建测试服务器,模拟真实请求:
public class ParticipantsApiTests : IClassFixture<WebApplicationFactory<Program>> { private readonly HttpClient _client; public ParticipantsApiTests(WebApplicationFactory<Program> factory) { // 替换DI容器中的真实服务为Mock var mockFactory = factory.WithWebHostBuilder(builder => { builder.ConfigureServices(services => { var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(ParticipantService)); if (descriptor != null) services.Remove(descriptor); var mockService = new Mock<ParticipantService>(); mockService.Setup(s => s.ListAllAsync(It.IsAny<GetAllParticipantsRequest>())) .ReturnsAsync(new List<ParticipantSummaryModel> { ParticipantMockData.NewParticipantModel() }); services.AddSingleton(mockService.Object); }); }); _client = mockFactory.CreateClient(); } [Fact] public async Task GetAllParticipants_ReturnsOkStatus() { // 准备请求内容 var query = new GetAllParticipantsRequest() { Name= "", KeyCordinator="", RelatedConnection="", Statuses = null }; var content = new StringContent(JsonSerializer.Serialize(query), Encoding.UTF8, "application/json"); // 发送POST请求 var response = await _client.PostAsync("/api/participants-all", content); // 断言 response.EnsureSuccessStatusCode(); Assert.Equal(HttpStatusCode.OK, response.StatusCode); var returnedParticipants = await JsonSerializer.DeserializeAsync<List<ParticipantSummaryModel>>( await response.Content.ReadAsStreamAsync(), new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); returnedParticipants.Should().NotBeEmpty(); } }
内容的提问来源于stack exchange,提问作者hanushi-thana
相关产品推荐
相关产品推荐

