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
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:
Then put them all in the constructor:
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.
Move all dependencies to a single ctor:
And then pass all values to it in test:
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 toProductController
ctor, but you can reduce the overall number of them and manage non-shared ones only where they are needed:Note that in general injecting both
IMediator
and things like db contexts and separate services, and looks like a smell, common approach is either useIMediator
for (almost) everything if using it at all.