skip to Main Content

I have an API upon which I mean to do Unit Testing via xUnit and Moq. It had one constructor actually, but I had to add another one for the specific purpose of Testing as per a suggestion by Visual Studio. It worked perfect for Testing, but I encountered another error once I tried to test the API via Swagger.
"System.InvalidOperationException: Multiple constructors accepting all given argument types have been found in type ‘Product.API.Controllers.ProductController’. There should only be one applicable constructor".
below is the contents of the controller

public class ProductController : ControllerBase
{
    private readonly IMediator _mediator;
    private readonly IConfiguration _configuration;
    private readonly UserManager<User> _userManager;
    private readonly SignInManager<User> _signInManager;
    private readonly ProductDbContext _productDbContext;
    private readonly IUserService _userService;

    public ProductController(IConfiguration configuration, UserManager<User> userManager, ProductDbContext productDbContext
        , SignInManager<User> signInManager, IUserService userService)
    {
        _configuration = configuration;
        _userManager = userManager;
        _productDbContext = productDbContext;
        _signInManager = signInManager;
        _userService = userService;
    }
    public ProductController(IMediator mediator)
    {
        _mediator = mediator;
    }
    [HttpGet]
    [AllowAnonymous]
    public async Task<IActionResult> GetAllProducts()
    {
        var products = await _mediator.Send(new GetAllProductsQuery());
        return Ok(products);
    }

Below is the code for Unit Testing

private readonly Mock<IMediator> _mediatorMock;
private readonly ProductController _controller;
public ProductControllerTest()
{
    _mediatorMock = new Mock<IMediator>();
    _controller = new ProductController(_mediatorMock.Object);
}
[Fact]
public async Task ProductController_GetAllProducts_ShouldReturnStatusCode200()
{
    //arrange
    var expected = new List<ProductModelDTO>();
    _mediatorMock.Setup(m=> m.Send(It.IsAny<GetAllProductsQuery>(), default(CancellationToken))).ReturnsAsync(expected);
    //act
    var result = await _controller.GetAllProducts();
    //assert
    var okResult = result as OkObjectResult;
    okResult?.StatusCode.Should().Be(200);
    okResult?.Value.Should().BeOfType<List<ProductModelDTO>>();
}

I came across a possible solution; to add a [FromServices] to the second constructor. But I got the same error!

2

Answers


  1. At a C# language level there’s nothing wrong with multiple constructors. However, at a framework level (where the dependency injector used in ASP.NET is trying to create an instance of the controller automatically), multiple constructors are confusing the framework.

    Use one constructor.

    If your controller has these dependencies:

    private readonly IMediator _mediator;
    private readonly IConfiguration _configuration;
    private readonly UserManager<User> _userManager;
    private readonly SignInManager<User> _signInManager;
    private readonly ProductDbContext _productDbContext;
    private readonly IUserService _userService;
    

    Then put them all in the constructor:

    public ProductController(
      IConfiguration configuration,
      UserManager<User> userManager,
      ProductDbContext productDbContext,
      SignInManager<User> signInManager,
      IUserService userService,
      IMediator mediator)
    {
        _configuration = configuration;
        _userManager = userManager;
        _productDbContext = productDbContext;
        _signInManager = signInManager;
        _userService = userService;
        _mediator = mediator;
    }
    

    That way the framework will know which constructor to use, because there’s only one. Any time you need to add a new dependency, add that dependency to the constructor instead of adding an entirely new constructor for just that one dependency.

    Login or Signup to reply.
  2. Move all dependencies to a single ctor:

    public ProductController(IConfiguration configuration, 
        UserManager<User> userManager, 
        ProductDbContext productDbContext, 
        SignInManager<User> signInManager, 
        IUserService userService, 
        IMediator mediator)
    {
        // ...
    }
    

    And then pass all values to it in test:

    public ProductControllerTest()
    {
        _mediatorMock = new Mock<IMediator>();
        // mock and pass everything:
        _controller = new ProductController(..., _mediatorMock.Object); 
    }
    

    Alternatively you can use the ASP.NET Core ability to use method injection (Action injection with FromServices) though you still will need to pass all common parameters to ProductController ctor, but you can reduce the overall number of them and manage non-shared ones only where they are needed:

    public ProductController(IConfiguration configuration, UserManager<User> userManager, ProductDbContext productDbContext
        , SignInManager<User> signInManager, IUserService userService)
    {
        _configuration = configuration;
        _userManager = userManager;
        _productDbContext = productDbContext;
        _signInManager = signInManager;
        _userService = userService;
    }
    
    [HttpGet]
    [AllowAnonymous]
    public async Task<IActionResult> GetAllProducts([FromServices] IMediator mediator)
    {
        var products = await _mediator.Send(new GetAllProductsQuery());
        return Ok(products);
    }
    

    Note that in general injecting both IMediator and things like db contexts and separate services, and looks like a smell, common approach is either use IMediator for (almost) everything if using it at all.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search