3

I am testing an ASP.NET Core Api post method, but I get a System.NullReferenceException causing my test to fail.

The null exception appears when I try to return a Created()-ActionResult, like this:

return Created(new Uri(Request.GetEncodedUrl() + "/" + personDto.Id), personDto);

My personDto.Id nor the personDto is not null, but the result of the Uri() returns null. I have googled but not found a solution, but I believe I have to mock the Uri()-method in some way (I am new to testing and asp.net in general). The Api works well when testing it "live" against a SQL Server.

I am using Moq and NUnit to test, and below is part of my Setup and Test-methods:

public void SetUp()
{
    _mapper = GenerateConcreteInstance();

    _personService = new Mock<IPersonService>();

    _personService.Setup(ps => ps.Create(It.IsAny<Person>())).Verifiable();

    _controller = new PeopleController(_personService.Object, _mapper);
}

[Test]
public void CreatePerson_WhenCalled_IsNotNull()
{
    var personDto = new PersonDto()
    {
        Id = 3,
        Firstname = "Karl",
        Lastname = "Karlsson",
        City = "Oslo",
        Email = "karl.karlsson@test.com"
    };

    var result = _controller.CreatePerson(personDto);

    var createdResult = result as CreatedResult;

    Assert.IsNotNull(createdResult);
}

And my controller that I am trying to test:

[HttpPost]
public IActionResult CreatePerson(PersonDto personDto)
{
    if (!ModelState.IsValid)
        return BadRequest();

    var person = _mapper.Map<PersonDto, Person>(personDto);
    person.DateCreated = DateTime.Now;

    _personService.Create(person);
    _personService.Save();

    personDto.Id = person.Id;

    return Created(new Uri(Request.GetEncodedUrl() + "/" + personDto.Id), personDto);
}

How do I setup this test properly to avoid the null exception? Would appreciate any help on how to solve this problem, and pointers in general, if my test is not good in any other way.

Update

I have modifed the test like this. Am I on the right track?

public void CreatePerson_WhenCalled_IsNotNull()
{
    var personDto = new PersonDto()
    {
        Id = 3,
        Firstname = "Karl",
        Lastname = "Karlsson",
        City = "Oslo",
        Email = "karl.karlsson@test.com"
    };

    var request = new Mock<HttpRequest>();
    request.Setup(x => x.Headers).Returns(
        new HeaderDictionary {
        {"X-Requested-With", "XMLHttpRequest"}
    });


    var context = new Mock<HttpContext>();
    context.Setup(x => x.Request).Returns(request.Object);

    _controller.ControllerContext = new ControllerContext()
    {
        HttpContext = context.Object
    };

    var result = _controller.CreatePerson(personDto);

    var createdResult = result as CreatedResult;

    Assert.IsNotNull(createdResult);
}
Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • I would expect that it's `Request` that is null. There are a number of posts on SO about mocking `Request`, including [How to mock the Request on Controller in ASP.Net MVC?](https://stackoverflow.com/q/970198/215552) – Heretic Monkey Oct 29 '19 at 19:11
  • @HereticMonkey Thanks. I am looking at that post and trying it out. However, I have to convert it to ASP.NET Core. You wouldn't have an idea of how to write convert this line in the top answer: controller.ControllerContext = new ControllerContext(context.Object, new RouteData(), controller); – Daniel Johansson Oct 29 '19 at 19:55
  • Try [Mock HttpRequest in ASP.NET Core Controller](https://stackoverflow.com/q/50116753/215552) :) – Heretic Monkey Oct 29 '19 at 20:08
  • @DanielJohansson `Request.GetEncodedUrl()` extension relies on having a valid request. There now context or request associated with the shown example. – Nkosi Oct 30 '19 at 01:33
  • @Nkosi Thanks for your comment. I have tried to apply the two examples Heretic Monkey linked to above, and have updated my question above (at the bottom). Would you care to take a look? Haven't gotten the test to pass yet though... – Daniel Johansson Oct 30 '19 at 17:10

1 Answers1

4

Request.GetEncodedUrl() extension relies on having a valid request.

/// <summary>
/// Returns the combined components of the request URL in a fully escaped form suitable for use in HTTP headers
/// and other HTTP operations.
/// </summary>
/// <param name="request">The request to assemble the uri pieces from.</param>
/// <returns>The encoded string version of the URL from <paramref name="request"/>.</returns>
public static string GetEncodedUrl(this HttpRequest request)
{
    return BuildAbsolute(request.Scheme, request.Host, request.PathBase, request.Path, request.QueryString);
}

Source

Which will fail

if (scheme == null)
{
    throw new ArgumentNullException(nameof(scheme));
}

Source

There is no context or request associated with the shown example so I suggest providing a valid context with the necessary members populated for the test to be exercised as expected.

//...omitted for brevity

var httpContext = new DefaultHttpContext();
//http://localhost/example
httpContext.Request.Scheme = "http";
httpContext.Request.Host = new HostString("localhost");

//Controller needs a controller context 
var controllerContext = new ControllerContext() {
    HttpContext = httpContext,
};
//assign context to controller
_controller.ControllerContext = controllerContext;

//...omitted for brevity
Nkosi
  • 235,767
  • 35
  • 427
  • 472