控制器单元测试示例

2022年3月30日 1560点热度 0人点赞 0条评论
内容纲要

使用接口的另一个好处是,方便 mock。
要检验自己代码是否好,足够分离,是否过度设计等,给自己写完整的单元测试。
如果自己写的代码,单元测试很难写或者无从下手,那么就要考虑改进代码了。
file

首先在单元测试类中,mock 注入服务:


public class BasketWebApiTest
{
    private readonly Mock<IBasketRepository> _basketRepositoryMock;
    private readonly Mock<IBasketIdentityService> _identityServiceMock;
    private readonly Mock<IEventBus> _serviceBusMock;
    private readonly Mock<ILogger<BasketController>> _loggerMock;

    public BasketWebApiTest()
    {
        _basketRepositoryMock = new Mock<IBasketRepository>();
        _identityServiceMock = new Mock<IBasketIdentityService>();
        _serviceBusMock = new Mock<IEventBus>();
        _loggerMock = new Mock<ILogger<BasketController>>();
    }
}

然后开始真正 mock 一个方法和参加 Controller 并测试。


    [Fact]
    public async Task Get_customer_basket_success()
    {
        //Arrange
        var fakeCustomerId = "1";
        var fakeCustomerBasket = GetCustomerBasketFake(fakeCustomerId);

        _basketRepositoryMock.Setup(x => x.GetBasketAsync(It.IsAny<string>()))
            .Returns(Task.FromResult(fakeCustomerBasket));
        _identityServiceMock.Setup(x => x.GetUserIdentity()).Returns(fakeCustomerId);

        _serviceBusMock.Setup(x => x.Publish(It.IsAny<UserCheckoutAcceptedIntegrationEvent>()));

        //Act
        var basketController = new BasketController(
            _loggerMock.Object,
            _basketRepositoryMock.Object,
            _identityServiceMock.Object,
            _serviceBusMock.Object);

        var actionResult = await basketController.GetBasketByIdAsync(fakeCustomerId);

        //Assert
        Assert.Equal((actionResult.Result as OkObjectResult).StatusCode, (int)System.Net.HttpStatusCode.OK);
        Assert.Equal((((ObjectResult)actionResult.Result).Value as CustomerBasket).BuyerId, fakeCustomerId);
    }

也可以 mock 函数的参数:

        _basketServiceMock.Setup(x => x.AddItemToBasket(It.IsAny<ApplicationUser>(), It.IsAny<Int32>()))
            .Returns(Task.FromResult(1));

如果正常注入服务,直接 new Controller 即可,但是如果 Controller 依赖于 HttpContext(这个可以不通过依赖注入使用),因为可以通过 ControllerBase 继承而来。

    [Controller]
    public abstract class ControllerBase
    {
        public HttpContext HttpContext
        public HttpRequest Request
        public HttpResponse Response
        public RouteData RouteData
        public ModelStateDictionary ModelState
        [ControllerContext]
        public ControllerContext ControllerContext
        public IModelMetadataProvider MetadataProvider
        public IModelBinderFactory ModelBinderFactory
        public IUrlHelper Url
        public IObjectModelValidator ObjectValidator
        public ProblemDetailsFactory ProblemDetailsFactory
        public ClaimsPrincipal User

那么除了 mock 注入的服务,还需要 mock ControllerBase 中的一些属性对象。

        //Act
        var orderController = new CartController(_basketServiceMock.Object, _catalogServiceMock.Object, _identityParserMock.Object);
        orderController.ControllerContext.HttpContext = _contextMock.Object;
        var actionResult = await orderController.AddToCart(fakeCatalogItem);

单元测试的名字可以长一些:

    public async Task Get_get_all_catalogitems_and_response_ok_status_code()

    public async Task Get_get_catalogitem_by_id_and_response_ok_status_code()

    public async Task Get_get_catalogitem_by_id_and_response_bad_request_status_code()

    public async Task Get_get_catalogitem_by_id_and_response_not_found_status_code()

    public async Task Get_get_catalogitem_by_name_and_response_ok_status_code()

    public async Task Get_get_paginated_catalogitem_by_name_and_response_ok_status_code()

如果只是测试会不会出现错误,而不检查返回值,则这样写最简洁:

        using (var server = CreateServer())
        {
            var response = await server.CreateClient()
                .GetAsync("api/v1/catalog/catalogtypes");

            response.EnsureSuccessStatusCode();
        }

痴者工良

高级程序员劝退师

文章评论