Search code examples
c#unit-testingasp.net-core-mvccontroller-action

How to unit test whether a Core MVC controller action calls ControllerBase.Problem()


We have a controller that derives from ControllerBase with an action like this:

public async Task<ActionResult> Get(int id)
{
  try
  {
    // Logic
    return Ok(someReturnValue);
  }
  catch
  {
    return Problem();
  }
}

We also have a unit test like this:

[TestMethod]
public async Task GetCallsProblemOnInvalidId()
{
  var result = sut.Get(someInvalidId);

}

But ControllerBase.Problem() throws a Null Reference Exception. This is a method from the Core MVC framework, so I don't realy know why it is throwing the error. I think it may be because HttpContext is null, but I'm not sure. Is there a standardized way to test a test case where the controller should call Problem()? Any help is appreciated. If the answer involves mocking: we use Moq and AutoFixtrue.


Solution

  • The null exception is because of a missing ProblemDetailsFactory

    In this case the controller needs to be able to create ProblemDetails instance via

    [NonAction]
    public virtual ObjectResult Problem(
        string detail = null,
        string instance = null,
        int? statusCode = null,
        string title = null,
        string type = null)
    {
        var problemDetails = ProblemDetailsFactory.CreateProblemDetails(
            HttpContext,
            statusCode: statusCode ?? 500,
            title: title,
            type: type,
            detail: detail,
            instance: instance);
    
        return new ObjectResult(problemDetails)
        {
            StatusCode = problemDetails.Status
        };
    }
    

    Source

    ProblemDetailsFactory is a settable property

    public ProblemDetailsFactory ProblemDetailsFactory
    {
        get
        {
            if (_problemDetailsFactory == null)
            {
                _problemDetailsFactory = HttpContext?.RequestServices?.GetRequiredService<ProblemDetailsFactory>();
            }
    
            return _problemDetailsFactory;
        }
        set
        {
            if (value == null)
            {
                throw new ArgumentNullException(nameof(value));
            }
    
            _problemDetailsFactory = value;
        }
    }
    

    Source

    that could be mocked and populated when testing in isolation.

    [TestMethod]
    public async Task GetCallsProblemOnInvalidId() {
        //Arrange
        var problemDetails = new ProblemDetails() {
            //...populate as needed
        };
        var mock = new Mock<ProblemDetailsFactory>();
        mock
            .Setup(_ => _.CreateProblemDetails(
                It.IsAny<HttpContext>(),
                It.IsAny<int?>(),
                It.IsAny<string>(),
                It.IsAny<string>(),
                It.IsAny<string>(),
                It.IsAny<string>())
            )
            .Returns(problemDetails)
            .Verifyable();
    
        var sut = new MyController(...);
        sut.ProblemDetailsFactory = mock.Object;
    
        //...
    
        //Act
        var result = await sut.Get(someInvalidId);
    
        //Assert
        mock.Verify();//verify setup(s) invoked as expected
    
        //...other assertions
    }