Example of Controller Unit Testing

2022年3月30日 66点热度 0人点赞 0条评论
内容目录

Using interfaces has another benefit: it facilitates mocking.

To evaluate whether your code is good, properly separated, or over-designed, write comprehensive unit tests for yourself. If it's difficult or impossible to write unit tests for your own code, then you should consider refactoring it.

file

First, inject mocks into the unit test class:

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>>();
    }
}

Next, start mocking a method and interacting with the Controller for testing.

[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);
}

You can also mock function parameters:

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

When services are normally injected, you can simply use new Controller, but if the Controller depends on HttpContext (which can be used without dependency injection), it can inherit from 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
}

Thus, in addition to mocking injected services, you also need to mock some properties of ControllerBase.

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

Unit test names can be longer:

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()

If you are only testing that no errors occur without checking return values, you can write it as follows:

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

    response.EnsureSuccessStatusCode();
}

痴者工良

高级程序员劝退师

文章评论