You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET 7 C#控制器测试含MediatR循环时返回500错误求助

问题排查:WASM C#应用中ContactController测试返回500错误

测试ContactController的ReturnsSuccessResult()用例时出现500内部服务器错误,移除GetContactListQueryHandler中映射州ID与缩写的foreach循环后测试恢复正常,需定位问题原因。


相关代码

ContactListVm

public class ContactListVm
{
  public Guid ContactId { get; set; }
  public string FirstName { get; set; } = string.Empty;
  public string LastName { get; set; } = string.Empty;
  public string NameSuffix {get; set;} = string.Empty;
  public string? MiddleName { get; set; }
  public string? CompanyName { get; set; }
  public string? Title { get; set; }
  public string Address1 { get; set; } = string.Empty;
  public string? Address2 { get; set; }
  public string? Address3 { get; set; }
  public string City { get; set; } = string.Empty;
  public Guid StateId { get; set; }
  public string? StateAbbreviation { get; set; }
  public string ZipCode { get; set; } = string.Empty;
  public string? Email { get; set; }
  public string PhoneNumber { get; set; } = string.Empty;
  public string? Fax { get; set; }
}

GetContactListQuery

public class GetContactListQuery : IRequest<List<ContactListVm>>
{
    
}

GetContactListQueryHandler

public GetContactListQueryHandler(IMapper mapper, IContactRepository contactRepository,
        IStateRepository stateRepository)
{
        _mapper = mapper;
        _contactRepository = contactRepository;
        _stateRepository = stateRepository;
}
public async Task<List<ContactListVm>> Handle(GetContactListQuery request, CancellationToken 
        cancellationToken)
{
        var allContacts = (await _contactRepository.ListAllAsync())
            .OrderBy(c => c.LastName);

        var newContactList = new List<ContactListVm>();

        foreach (var contact in allContacts)  // 移除该循环后错误消失
        {
            var contactDetailDto = _mapper.Map<ContactListVm>(contact);
            if (contactDetailDto.StateId != Guid.Empty)
            {
                var state = await _stateRepository.GetByIdAsync(contactDetailDto.StateId);
                contactDetailDto = _mapper.Map(state, contactDetailDto);
            }
            newContactList.Add(contactDetailDto);
        }

  return newContactList;
}

Contact Controller

[Route("api/[controller]")]
[ApiController]
public class ContactController : ControllerBase
{
private readonly IMediator _mediator;

public ContactController(IMediator mediator)
{
  _mediator = mediator;
}

[HttpGet("all", Name ="GetAllContacts")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<List<ContactListVm>>> GetAllContacts()
{
   var dtos = await _mediator.Send(new GetContactListQuery());
   return Ok(dtos);
}

[HttpGet("{id}", Name = "GetContactById")]
public async Task<ActionResult<ContactDetailVm>> GetContactById(Guid id)
{
    var getContactDetailQuery = new GetContactDetailQuery() { Id = id };
    return Ok(await _mediator.Send(getContactDetailQuery));
}

[HttpPost(Name = "AddContact")]
public async Task<ActionResult<CreateContactCommandResponse>> Create
            ([FromBody] CreateContactCommandResponse createContactCommand)
{
            var response = await _mediator.Send(createContactCommand);
            return Ok(response);
}
}

Contact Controller Tests

[Collection("Controllers Test Collection")]
public class ContactControllerTests : IClassFixture<CustomWebApplicationFactory<Program>>
{
 private readonly CustomWebApplicationFactory<Program> _factory;
 private readonly Mock<IMediator> _mediatorMock;
 private readonly ContactController _contactController;
    
 public ContactControllerTests(CustomWebApplicationFactory<Program> factory)
 {
    _factory = factory;
    _mediatorMock = new Mock<IMediator>();
    _contactController = new ContactController(_mediatorMock.Object);
 }
    
[Fact]
public async Task ReturnsSuccessResult()    //<-- 此测试出现500内部服务器错误
{
            var client = _factory.GetAnonymousClient();
    
            var response = await client.GetAsync("/api/contact/all");
    
            response.EnsureSuccessStatusCode();
    
            var responseString = await response.Content.ReadAsStringAsync();
    
            var result = JsonSerializer.Deserialize<List<ContactListVm>>(responseString);
    
            Assert.IsType<List<ContactListVm>>(result);
            Assert.NotEmpty(result);
}

GetContactListQueryHandlerTests

public class GetContactListQueryHandlerTests
{
    private readonly IMapper _mapper;
    private readonly Mock<IContactRepository> _mockContactRepository;
    private readonly Mock<IStateRepository> _mockStateRepository;

    public GetContactListQueryHandlerTests()
    {
        var contactRepositoryMock = new ContactRepositoryMock();
        _mockContactRepository = contactRepositoryMock.GetContactRepositoryMock();

        var stateRepositoryMock = new StateRepositoryMock();
        _mockStateRepository = stateRepositoryMock.GetStateRepositoryMock();

        var configurationProvider = new MapperConfiguration(cfg =>
        {
            cfg.AddProfile<ContactMappingProfile>();
            cfg.AddProfile<StateMappingProfile>();
        });
        _mapper = configurationProvider.CreateMapper();
    }

    [Fact]
    public async Task GetContactList_Result_IsOfTypeListContactListVm()
    {
        var handler = new GetContactListQueryHandler(_mapper,
            _mockContactRepository.Object, _mockStateRepository.Object);

        var result = await handler.Handle(new GetContactListQuery(),
            CancellationToken.None);

        result.ShouldBeOfType<List<ContactListVm>>();
    }
}

问题排查分析

核心问题点定位

移除foreach循环后测试恢复正常,说明错误出在循环内的逻辑,重点排查以下几点:

  1. StateRepository依赖未正确Mock
    集成测试用CustomWebApplicationFactory启动了完整应用,而非控制器测试中创建的_mediatorMock。如果测试环境中IStateRepository无法找到对应StateId的数据,会导致GetByIdAsync返回null,后续AutoMapper映射可能抛出空引用异常。

  2. AutoMapper映射错误
    当state为null时,调用_mapper.Map(state, contactDetailDto)可能触发异常,需检查StateMappingProfile是否处理了源为null的情况。

  3. 异步操作的线程/上下文问题
    循环内多次调用await _stateRepository.GetByIdAsync,如果测试环境中数据库连接或仓储实现存在异步处理问题,也可能导致500错误。

解决方案步骤

1. 查看测试环境的错误详情

修改测试代码,捕获并输出500错误的响应内容,明确异常信息:

[Fact]
public async Task ReturnsSuccessResult()
{
    var client = _factory.GetAnonymousClient();
    var response = await client.GetAsync("/api/contact/all");
    
    if (!response.IsSuccessStatusCode)
    {
        var errorContent = await response.Content.ReadAsStringAsync();
        Assert.Fail($"请求失败,状态码:{(int)response.StatusCode},错误信息:{errorContent}");
    }
    
    response.EnsureSuccessStatusCode();
    // 后续断言逻辑...
}

2. 确保测试环境中State数据存在

如果是真实数据库的集成测试,要保证测试数据中所有Contact的StateId都对应存在的State记录;如果使用Mock,需在CustomWebApplicationFactory中替换IStateRepository为Mock实现,确保GetByIdAsync返回有效数据。

3. 优化Handler逻辑,避免空引用

在Handler中增加null判断,防止state为null时触发映射异常:

if (contactDetailDto.StateId != Guid.Empty)
{
    var state = await _stateRepository.GetByIdAsync(contactDetailDto.StateId);
    if (state != null) // 增加null判断
    {
        contactDetailDto = _mapper.Map(state, contactDetailDto);
    }
}

4. 批量获取State数据,提升性能与稳定性

循环内多次调用仓储查询低效且易出问题,建议一次性获取所有需要的State,再批量映射:

public async Task<List<ContactListVm>> Handle(GetContactListQuery request, CancellationToken cancellationToken)
{
    var allContacts = (await _contactRepository.ListAllAsync())
        .OrderBy(c => c.LastName);
    
    // 收集所有非空的StateId
    var stateIds = allContacts.Where(c => c.StateId != Guid.Empty)
                              .Select(c => c.StateId)
                              .Distinct()
                              .ToList();
    
    // 批量获取State数据
    var states = await _stateRepository.GetByIdsAsync(stateIds);
    var stateDict = states.ToDictionary(s => s.StateId);
    
    var newContactList = allContacts.Select(contact => 
    {
        var vm = _mapper.Map<ContactListVm>(contact);
        if (vm.StateId != Guid.Empty && stateDict.TryGetValue(vm.StateId, out var state))
        {
            _mapper.Map(state, vm);
        }
        return vm;
    }).ToList();
    
    return newContactList;
}

内容的提问来源于stack exchange,提问作者TooStupidForStackOverflow

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 07:54:56