1

I'm writing tests for controllers actions of ASP.Net Core application. All controllers of my app inherited from base controller, let's call it MyBaseController. Constructor of this class looks like this:

public MyBaseController(ServiceA serviceA, ServiceB serviceB)
{
 m_serviceA = serviceA;
 m_serviceB = serviceB;
}

So any inherited class can be mocked with this:

protected TController MockedController<TController>() where TController: MyBaseController
{
var moqController = new Mock<TController>(serviceAMock, serviceBMock);

return moqController.Object;
}

This works fine until we have class which inherited from MyBaseController and require ServiceC serviceC additional argument.

When I tried to run tests for such controllers with extra arguments I've got an error: "Could not find a constructor that would match given arguments". Situation seems prety clear, but I have no idea how to write general costructor mock to avoid creation of specific methods for such classes (there are a lot of them with different constructor arguments).

Are any ways to achieve my goal? Or the best way is to write individual methods to mock classes with different arguments?

8
  • 1
    Your MockedController method just wraps the constructor anyway, so you could get rid of that altogether. Unless there's something you've omitted? Commented Jul 8 at 8:00
  • @JohnathanBarclay I've provided just basic functionallity of this method. In real it setup some mocked methods for MyControllerBase and replace some services for tests needs. Removing it is the last what I would do. Commented Jul 8 at 8:07
  • How about the method takes an already instantiated Mock<TController> in that case, to setup the methods? Commented Jul 8 at 8:14
  • "Or the best way is to write individual methods to mock classes with different arguments?" Presumably yes. If they have identical behavior so they can be tested by the same testsetup why having multiple controller/test at all? There might be a small subset of infrastructural methods that might be tested this way but the majority of methods should tend to need individual tests to be relevant.
    – Ralf
    Commented Jul 8 at 8:24
  • 1
    I don't understand. If you want to test the controller, why do you mock it? You should only mock the services.
    – Palle Due
    Commented Jul 8 at 9:23

2 Answers 2

1

A typical design progression usually starts out like that, then proceeds over a variation of the Object Mother pattern (adding method overloads to MockedController in this case), to perhaps a kind of Fluent Builder or auto-mocking container.

Ultimately, you should hopefully come to the realization that while all of these are viable solutions for narrow problems, in general they are symptoms of over-mocking, which again suggests that the System Under Test may be too complicated.

In this case, for example, why do you need both a base Controller and injected dependencies?

1
  • About why I need dependancies and base controller: there are a lot of common functionallity in all of my controllers and much easer to keep it in one place, but some controller use special services and I think that it's not needed to put all this services base controller class Commented Jul 10 at 6:39
1

Here I got a sample as a dynamic constructor.

SampleController.cs

[ApiController]
[Route("[controller]")]
public class SampleController : MyBaseController
{
    private readonly IServiceC _serviceC;
    private readonly IServiceD _serviceD;
    public SampleController(
        IServiceA serviceA, 
        IServiceB serviceB, 
        IServiceC serviceC,
        IServiceD serviceD)
    :base(serviceA, serviceB)
    {
        _serviceC = serviceC;
        _serviceD = serviceD;
    }

    public IActionResult Get()
    {
        _serviceA.ActionA();
        _serviceB.ActionB();
        _serviceC.ActionC();
        _serviceD.ActionD();
        return Ok();
    }
}

One of my services defined

public interface IServiceC
{
    void ActionC();
}

public class ServiceC : IServiceC
{
    public void ActionC()
    {
        Console.WriteLine("ActionC executed");
    }
}

Register the services

builder.Services.AddScoped<IServiceA, ServiceA>();
builder.Services.AddScoped<IServiceB, ServiceB>();
builder.Services.AddScoped<IServiceC, ServiceC>();
builder.Services.AddScoped<IServiceD, ServiceD>();

SampleControllerTests.cs in test

public class SampleControllerTests
{
    protected TController MockedController<TController>(params object[] Cons) where TController : MyBaseController
    {
        var constructor = typeof(TController).GetConstructors()
            .FirstOrDefault(cons =>
            {
                var parameters = cons.GetParameters();
                if (parameters.Length != Cons.Length)
                    return false;

                for (int i = 0; i < parameters.Length; i++)
                {
                    if (!parameters[i].ParameterType.IsInstanceOfType(Cons[i]))
                        return false;
                }
                return true;
            });

        if (constructor == null)
            throw new ArgumentException("Constructor should not be null");

        return (TController)constructor.Invoke(Cons);
    }

    [Fact]
    public void Get_ReturnsSuccess()
    {
        var serviceAMock = new Mock<IServiceA>();
        var serviceBMock = new Mock<IServiceB>();
        var serviceCMock = new Mock<IServiceC>();
        var serviceDMock = new Mock<IServiceD>();

        var controller = MockedController<SampleController>(
            serviceAMock.Object, 
            serviceBMock.Object, 
            serviceCMock.Object,
            serviceDMock.Object);

        var result = controller.Get();

        var okResult = Assert.IsType<OkObjectResult>(result);
        Assert.Equal("Success", okResult.Value);
    }
}

Run Test

enter image description here

1
  • Thanks for answerring. I used same approach in the end Commented Jul 11 at 8:54

Not the answer you're looking for? Browse other questions tagged or ask your own question.