jeudi 30 mars 2023

Unit testing - scoping and best practice

I'm implementing a API project on .net core, the solution follows simple three layer architecture as described below.

  1. Orders.Api --> Function App that implements controllers, security, exception handler, etc.
  2. Orders.Service --> Class library that implements all business logic (validations, application flow, models, etc)
  3. Orders.Repository --> Class library that implements repos to INFRA like database, service bus, etc.

One of Api (POST Orders) is implemented as shown below.

    public class OrdersController
    {
        private readonly IHttpTriggerHelper _httpTriggerHelper;
        private readonly IOrderService _OrderService;        

        public OrdersController(IHttpTriggerHelper httpTriggerHelper, IOrderService OrderService)
        {
            _httpTriggerHelper = httpTriggerHelper;
            _OrderService = OrderService;
        }

        [Function("CreateOrder")]
        public async Task<HttpResponseData> CreateOrder([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "dealers/{dealerId:int}/Orders")] HttpRequestData req, int dealerId)
        {               
            var OrderRequest = await _httpTriggerHelper.GetRequestModel<OrderData>(req, cancellationToken);                                    
            var userId = _httpTriggerHelper.GetContextData(req, "User.Id") ?? string.Empty;
            var OrderResponse = await _OrderService.SendOrderRequest(dealerId, userId, OrderRequest, cancellationToken);

            return await _httpTriggerHelper.GetHttpResponse(OrderResponse, HttpStatusCode.Accepted, req, cancellationToken);
        }
    }

public class OrderService : IOrderService
    {
        private readonly IOrderRepository _orderRepository;
        private readonly IValidationManager _validator;

        public OrderService(IOrderRepository orderRepository, IValidationManager validator)
        {
            _orderRepository = orderRepository;
            _validator = validator;
        }

        public async Task<OrderResponse> SendOrderRequest(int dealerId, string userId, OrderData? orderData)
        {
            _validator.ValidateOrderRequest(orderData);

            orderData.EnrichWithRequestContextData(dealerId, userId);

            await _orderRepository.SendOrderRequest(orderData);

            return new OrderResponse { CorrelationId = orderData.CorrelationId, Status = orderData.Status };
        }
    }

I'm struggling to scope the contract and unit testing for OrderService.SendOrderRequest.

Here is my first approach on unit testing scope.

  1. OrderService must validate the orderData (Ensure validate method is called)
  2. orderData is enriched with request context data.
  3. orderData is stored with orderRepository.
  4. OrderResponse is built as expected.

Here is my second approach on unit testing scope.

  1. OrderService must validate the orderData (Ensure validate method is called)
  2. OrderService must throw exception when the orderData is not valid.
  3. orderData is enriched with request context data.
  4. orderData is stored with orderRepository.
  5. OrderService must throw exception when the failed to store the orderData.
  6. OrderResponse is built as expected.

The second approach seems to make the OrderService more robust. The question I've is on the points 2 and 5.

  1. Is it a general/good practice to unit test all possible exceptions thrown from dependencies?
  2. If so, this expectation is also applicable to upper layers (ex; Api controller)?

Please share if any other better approaches also.

Aucun commentaire:

Enregistrer un commentaire