【问题标题】:How to write unit tests for asp.net core web api controller如何为 asp.net core web api 控制器编写单元测试
【发布时间】:2021-01-24 10:50:36
【问题描述】:

所以我对编写测试非常陌生。我创建了一个 ASP.NET 核心 web api 以及 angular。我必须为 Web API 控制器编写单元测试。我一直在阅读有关如何开始对 ASP.NET Web API 进行单元测试的 Microsoft 文档。但是我仍然非常不确定如何编写适当的测试。

我的控制器代码

[Authorize]
[Route("api/[controller]")]
[ApiController]
public class OrdersController : ControllerBase
{
    private readonly IOrderRepository _repo;
    private readonly IMapper _mapper;
    public OrdersController(IOrderRepository repo, IMapper mapper)
    {
        _repo = repo;
        _mapper = mapper;
    }

    [AllowAnonymous]
    [HttpPost()]
    public async Task<IActionResult> AddOrder(OrderForMappingDto orderForMappingDto)
    {
        //if(orderForMappingDto.ARentalOrNot == null)
        //{
        //    throw new Exception("Value can't be left null");
        //}
        var orderToCreate = _mapper.Map<TblOrder>(orderForMappingDto);
        var createdOrder = await _repo.AddOrder(orderToCreate);

        return Ok(createdOrder);
    }

}

我的仓库代码

public class OrderRepository : IOrderRepository
{
    private readonly MovieRentalDBContext _context;

    public OrderRepository(MovieRentalDBContext context)
    {
        _context = context;
    }
    public async Task<TblOrder> AddOrder(TblOrder tblOrder)
    {
        await _context.TblOrder.AddAsync(tblOrder);
        await _context.SaveChangesAsync();
        return tblOrder;
    }
}

我知道有很多嘲笑要做。但是我是否也需要模拟实体框架? 我写了一个简单的测试文件。

public void PostsAorder_WhenCalled_ReturnsOkWithResponse()
{
    var mockOrderRepository = new Mock<IOrderRepository>();
    var mockOrderMapper = new Mock<IMapper>();
    var orderControllerObject = new OrdersController(mockOrderRepository.Object, mockOrderMapper.Object);

    Task<IActionResult> contentResult = orderControllerObject.AddOrder(new OrderForMappingDto
    {
        ACustomerId = 3,
        AMovieId = 18,
        ARentalOrNot = false,
        AOrderedDate = DateTime.Now
    }) ;
    //var contentResult = actionResult as OkNegotiatedContentResult<OrderForMappingDto>;
    Assert.IsNotNull(contentResult);
    Assert.IsNotNull(contentResult.Result);
}

OkNegotioatedContent 函数不适用于任务。我该如何使用它来完成任务。此外,即使在 DTO 中它们被归类为 [Required] 时,即使我没有提供最后 3 个参数,测试也会通过。有人可以帮助如何正确修改测试。

映射器配置-

【问题讨论】:

  • 您不需要模拟整个实体框架。 IMO 您可以使用现有的开发数据库并在测试运行完成后删除在测试期间创建的所有对象。但是,如果您不想这样做,您可以使用 in memory database 中的 EF 核心,它完全存在于内存中,并且所有内容都会在事后被删除
  • 另外,IMO,你也不需要模拟你的 repo 和 mapper,使用真实的东西。知道您的控制器方法是否适用于模拟数据对您有什么好处?您真正想知道的是您的控制器方法是否适用于真实数据
  • @MindSwipe 如果我使用真实的东西,那不就是将它们作为外部依赖的集成测试吗?
  • 嗯,它使测试(同样,IMO)更有价值。现在你唯一的测试是你的控制器不返回空值,而不是如果实际上做了它应该做的事情(应用验证、映射和保存)。老实说,我从不关心单元测试的分类,只是确保我编写的所有代码都经过适当测试,这意味着至少有一个正面测试和一个负面测试,它们都涵盖了我能想到的所有边缘情况

标签: c# asp.net unit-testing mocking asp.net-core-webapi


【解决方案1】:

您的测试几乎没问题。首先是固定版本,然后是一些解释:

[Fact]
public async Task GivenAValidOrder_WhenICallTheAsOrder_ThenItReturnsOkWithResponse()
{
    //Arrange
    var mockOrderMapper = new Mock<IMapper>();
    mockOrderMapper.Setup(mapper => mapper.Map<TblOrder>(It.IsAny<OrderForMappingDto>()))
        .Returns(new TblOrder());

    var mockOrderRepository = new Mock<IOrderRepository>();
    mockOrderRepository.Setup(repo => repo.AddOrder(It.IsAny<TblOrder>()))
        .ReturnsAsync((TblOrder order) => order);

    var SUT = new OrdersController(mockOrderRepository.Object, mockOrderMapper.Object);

    //Act
    var contentResult = await SUT.AddOrder(new OrderForMappingDto
    {
        ACustomerId = 3,
        AMovieId = 18,
        ARentalOrNot = false,
        AOrderedDate = DateTime.Now
    });

    //Assert
    Assert.NotNull(contentResult);
    Assert.IsAssignableFrom<OkObjectResult>(contentResult);

    var result = ((OkObjectResult)contentResult).Value;
    Assert.NotNull(result);
    Assert.IsAssignableFrom<TblOrder>(result);
}
  1. test 的名称遵循 Given When Then 结构,以便更容易理解在什么情况下控制器的操作应该如何表现。
  2. 测试现在是异步的,因为我们需要 await 完成控制器的操作才能检查其结果。
  3. 我已将Arrange Act Assert cmets 添加到代码中,以强调哪个阶段从何时开始。
  4. 我已将映射器模拟设置为返回一个新的 TblOrder,并将存储库模拟设置为返回它收到的任何内容。
  5. 我已将orderControllerObject 重命名为SUT,因为它强调了正在检查的组件(被测系统)。
  6. 在结果验证中,我使用了IsAssingableForm 而不是IsType,因为它也会检查派生类。在这种特殊情况下,这不是强制性的,但这是一种很好的做法。
  7. 结果类型将是 OkObjectResult 而不是 OkNegotiatedContentResult,因此您应该检查一下。
  8. 最后我添加了一个额外的检查,以确保返回的对象的类型是预期的。

【讨论】:

  • 嘿,感谢您的回答,但出现两个错误,我尝试进行更改,但错误只会改变。 1) 无法将类型 'MovieRentalApp.Models.TblOrder' 隐式转换为 'System.Threading.Tasks.Task' MovieRentalApp.UnitTests 2) 无法将 lambda 表达式转换为预期的委托类型,因为其中的某些返回类型该块不能隐式转换为委托返回类型
  • 感谢 gor 让我知道。明天我会修复它。
  • @TusharGoel 我已经更新并测试了代码。给您带来的不便深表歉意。
  • 感谢大家的帮助
  • 嗨彼得,所以我尝试了你的解决方案,它修复了很多位,但我遇到了模拟映射器设置的问题。因为我在调试中发现,在 addOrder 函数中,它进入了控制器内部,并且映射函数返回了一个空对象。如何修复映射设置。 var orderToCreate = _mapper.Map(orderForMappingDto);返回空
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多