Search code examples
c#unit-testing.net-coremoqxunit.net

Mock ControllerBase Request using Moq


I have a Web API in .Net Core 2.2 as below:

[Authorize]
[Route("api/[controller]")]
[ApiController]
public class SomeController : ControllerBase
{
    [HttpPost]
    public async Task<string> SomeMethodPost()
    {
        string returnUrl = $"{this.Request.Scheme}://{this.Request.Host}{this.Request.PathBase}/some/redirect";

        //Some Third Part Service Call

        return serviceResult;
    }
}

I want to mock the properties "Scheme", "Host" and "PathBase" for my controller action in my unit test. I managed to write below code in my unit test method:

var request = new Mock<HttpRequest>(MockBehavior.Strict);
request.Setup(x => x.Scheme).Returns("http");
request.Setup(x => x.Host).Returns(HostString.FromUriComponent("http://localhost:8080"));
request.Setup(x => x.PathBase).Returns(PathString.FromUriComponent("/api"));

var mockHttp = new Mock<ControllerBase>(MockBehavior.Strict);
mockHttp.SetupGet(x => x.Request).Returns(request.Object);

However, the mock in last line throws exception as "Request" of "ControllerBase" is non overridable. I understand the limitation with non virtual properties of abstract classes. Is there any workaround for this?

Moq version is 4.13.0.


Solution

  • Change approach. Do not mock the subject under test, which in this case is the controller.

    The controller's Request is accessed via the HttpContext which can be set when arranging the test.

    For example

    //Arrange
    var request = new Mock<HttpRequest>();
    request.Setup(x => x.Scheme).Returns("http");
    request.Setup(x => x.Host).Returns(HostString.FromUriComponent("http://localhost:8080"));
    request.Setup(x => x.PathBase).Returns(PathString.FromUriComponent("/api"));
    
    var httpContext = Mock.Of<HttpContext>(_ => 
        _.Request == request.Object
    );
    
    //Controller needs a controller context 
    var controllerContext = new ControllerContext() {
        HttpContext = httpContext,
    };
    //assign context to controller
    var controller = new SomeController(){
        ControllerContext = controllerContext,
    };
    
    String expected = "expected value here";
    
    //Act
    String actual = await controller.SomeMethodPost();
    
    
    //Assert
    Assert.AreEqual(expected, actual);
    
    //...