6

I'm creating basic unit tests for my project. For some reason, I keep getting a NullReferenceException when testing that I get a ControllerBase.Problem(String, String, Nullable<Int32>, String, String) response. I'm sure the problem is a discrepancy from the controller not actually running, as it seems to behave perfectly fine when the controller is running.

Controller:

        [HttpGet("{id}")]
        [Produces("application/json")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public IActionResult GetPatient([GuidNotEmpty] Guid id)
        {
            Patient patient = null;

            patient = _patientDbService.FindPatient(id);
            if (patient == null) {
                return Problem("Patient not found.", string.Empty, StatusCodes.Status404NotFound,
                    "An error occurred.", "https://tools.ietf.org/html/rfc7231#section-6.5.1");
            }

            return Ok(patient);
        }

Test:

        [Fact]
        public void TestGetPatientFromIdPatientNotFound()

        {
            // Act
            IActionResult result = _patientController.GetPatient(Guid.NewGuid());

            // Assert
            Assert.IsType<ObjectResult>(result);
            Assert.NotNull(((ObjectResult)result).Value);
            Assert.IsType<ProblemDetails>(((ObjectResult)result).Value);
            Assert.Equal(((ObjectResult)result).StatusCode, StatusCodes.Status404NotFound);
        }

Result:

X PatientServiceTest.PatientServiceUnitTest.TestGetPatientFromIdPatientNotFound [1ms]
Error Message:
   System.NullReferenceException : Object reference not set to an instance of an object.
Stack Trace:
   at Microsoft.AspNetCore.Mvc.ControllerBase.Problem(String detail, String instance, Nullable`1 statusCode, String title, String type)
   at PatientService.Controllers.PatientController.GetPatient(Guid id) in /home/surafel/coding/microservices-dev/c#/PatientService/Controllers/PatientController.cs:line 43
   at PatientServiceTest.PatientServiceUnitTest.TestGetPatientFromIdPatientNotFound() in /home/surafel/coding/microservices-dev/c#/PatientServiceTest/PatientServiceUnitTest.cs:line 69
7
  • How have you initialized _patientController? How are you specifying its dependencies? ControllerBase relies on various framework level services. Commented Jul 14, 2020 at 16:28
  • I don't know much about this. But If I were you i'll 1. Try to debug the test and check variables 2. Remove all arguments to the Problem statement or event change to Ok May not resolve the problem but can help tracing the problem. Testing controllers is a little difficult as mentioned above by @AluanHaddad , it relies on a lot of other stuff Commented Jul 14, 2020 at 16:33
  • I'm specifying it's dependencies. I mocked the db service I used, and I haven't had any problems with it until now. I used to test for BadRequestObjectResult instead of just ObjectResult. Commented Jul 14, 2020 at 16:34
  • 1
    Here's the reason: github.com/dotnet/aspnetcore/blob/master/src/Mvc/Mvc.Core/src/… Commented Jul 14, 2020 at 16:48
  • What would be the standard way for fixing this? I wanted to have a consistent structure for my errors. Would encapsulating the Problem call in a try-catch loop, and create my own ProblemDetails be a proper solution? Commented Jul 14, 2020 at 18:06

1 Answer 1

9

As pointed out by Aluan Haddad in the comments, the that Problem() calls ProblemDetailsFactory to create the ProblemDetails objects, which is supplied by the service manager. The service manager only works when the application is running:https://github.com/dotnet/aspnetcore/blob/master/src/Mvc/Mvc.Core/src/ControllerBase.cs#L194

The ControllerBase.ProblemDetailsFactory variable can be set, so I created a mock ProblemDetailsFactory instance and set the controllers factory to an instance of my mock. This seems to make it work.

Mock:

    public class MockProblemDetailsFactory : ProblemDetailsFactory
    {
        public MockProblemDetailsFactory()
        {
        }

        public override ProblemDetails CreateProblemDetails(HttpContext httpContext,
            int? statusCode = default, string title = default,
            string type = default, string detail = default, string instance = default)
        {
            return new ProblemDetails() {
                Detail = detail,
                Instance = instance,
                Status = statusCode,
                Title = title,
                Type = type,
            };
        }

        public override ValidationProblemDetails CreateValidationProblemDetails(HttpContext httpContext,
            ModelStateDictionary modelStateDictionary, int? statusCode = default,
            string title = default, string type = default, string detail = default,
            string instance = default)
        {
            return new ValidationProblemDetails(new Dictionary<string, string[]>()) {
                Detail = detail,
                Instance = instance,
                Status = statusCode,
                Title = title,
                Type = type,
            };
        }
    }

I added this line in the setup for this unit tests, and it solves the problem.

_patientController.ProblemDetailsFactory = new MockProblemDetailsFactory();
Sign up to request clarification or add additional context in comments.

1 Comment

This is functional but really disappointing from an ergonomics perspective

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.