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!
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.