ChatGPT解决这个技术问题 Extra ChatGPT

How to mock the Request on Controller in ASP.Net MVC?

I have a controller in C# using the ASP.Net MVC framework

public class HomeController:Controller{
  public ActionResult Index()
    {
      if (Request.IsAjaxRequest())
        { 
          //do some ajaxy stuff
        }
      return View("Index");
    }
}

I got some tips on mocking and was hoping to test the code with the following and RhinoMocks

var mocks = new MockRepository();
var mockedhttpContext = mocks.DynamicMock<HttpContextBase>();
var mockedHttpRequest = mocks.DynamicMock<HttpRequestBase>();
SetupResult.For(mockedhttpContext.Request).Return(mockedHttpRequest);

var controller = new HomeController();
controller.ControllerContext = new ControllerContext(mockedhttpContext, new RouteData(), controller);
var result = controller.Index() as ViewResult;
Assert.AreEqual("About", result.ViewName);

However I keep getting this error:

Exception System.ArgumentNullException: System.ArgumentNullException : Value cannot be null. Parameter name: request at System.Web.Mvc.AjaxRequestExtensions.IsAjaxRequest(HttpRequestBase request)

Since the Request object on the controller has no setter. I tried to get this test working properly by using recommended code from an answer below.

This used Moq instead of RhinoMocks, and in using Moq I use the following for the same test:

var request = new Mock<HttpRequestBase>();
// Not working - IsAjaxRequest() is static extension method and cannot be mocked
// request.Setup(x => x.IsAjaxRequest()).Returns(true /* or false */);
// use this
request.SetupGet(x => x.Headers["X-Requested-With"]).Returns("XMLHttpRequest");

var context = new Mock<HttpContextBase>();
context.SetupGet(x => x.Request).Returns(request.Object);
var controller = new HomeController(Repository, LoginInfoProvider);
controller.ControllerContext = new ControllerContext(context.Object, new RouteData(), controller);
var result = controller.Index() as ViewResult;
Assert.AreEqual("About", result.ViewName);

but get the following error:

Exception System.ArgumentException: System.ArgumentException : Invalid setup on a non-overridable member: x => x.Headers["X-Requested-With"] at Moq.Mock.ThrowIfCantOverride(Expression setup, MethodInfo methodInfo)

Again, it seems like I cannot set the request header. How do I set this value, in RhinoMocks or Moq?

Replace Request.IsAjaxRequest with Request.IsAjaxRequest()
Mock Request.Headers["X-Requested-With"] or Request["X-Requested-With"] instead of Request.IsAjaxRequest(). I've updated my question

Y
Yogesh lele

Using Moq:

var request = new Mock<HttpRequestBase>();
// Not working - IsAjaxRequest() is static extension method and cannot be mocked
// request.Setup(x => x.IsAjaxRequest()).Returns(true /* or false */);
// use this
request.SetupGet(x => x.Headers).Returns(
    new System.Net.WebHeaderCollection {
        {"X-Requested-With", "XMLHttpRequest"}
    });

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

var controller = new YourController();
controller.ControllerContext = new ControllerContext(context.Object, new RouteData(), controller);

UPDATED:

Mock Request.Headers["X-Requested-With"] or Request["X-Requested-With"] instead of Request.IsAjaxRequest().


I get the message "The Type argument for method 'ISetupGetterMoq.Mock.SetupGet.... cannot be infered from uage. Try specifying the type arguments explicitly. What type do I set 'var request=' to though to get this to work?
Just updated my answer - not Request.IsAjaxRequest but Request.IsAjaxRequest(). Update your question too
Still generates: Exception System.ArgumentException: System.ArgumentException : Invalid setup on a non-overridable member: x => x.IsAjaxRequest() at Moq.Mock.ThrowIfCantOverride(Expression setup, MethodInfo methodInfo)
The problem is that IsAjaxRequest() is static extension method and cannot be mocked - I've updated my answer.
should be context.SetupGet(x => x.Request).Returns(request.Object); your code above is missing the 's' on Return still Also results in Exception System.ArgumentException: System.ArgumentException : Invalid setup on a non-overridable member: x => x.Headers["X-Requested-With"] at Moq.Mock.ThrowIfCantOverride(Expression setup, MethodInfo methodInfo) error message
J
Joel Christophel

For anyone using NSubstitute I was able to modify the above answers and do something like this... (where Details is the Action method name on the controller)

 var fakeRequest = Substitute.For<HttpRequestBase>();
 var fakeContext = Substitute.For<HttpContextBase>();
 fakeRequest.Headers.Returns(new WebHeaderCollection { {"X-Requested-With", "XMLHttpRequest"}});
 fakeContext.Request.Returns(fakeRequest);
 controller.ControllerContext = new ControllerContext(fakeContext, new RouteData(), controller);
 var model = new EntityTypeMaintenanceModel();
        
 var result = controller.Details(model) as PartialViewResult;
        
 Assert.IsNotNull(result);
 Assert.AreEqual("EntityType", result.ViewName);

P
Phil Hale

Here is a working solution using RhinoMocks. I've based it on a Moq solution I found at http://thegrayzone.co.uk/blog/2010/03/mocking-request-isajaxrequest/

public static void MakeAjaxRequest(this Controller controller)
{
        MockRepository mocks = new MockRepository();

        // Create mocks
        var mockedhttpContext = mocks.DynamicMock<HttpContextBase>();
        var mockedHttpRequest = mocks.DynamicMock<HttpRequestBase>();

        // Set headers to pretend it's an Ajax request
        SetupResult.For(mockedHttpRequest.Headers)
            .Return(new WebHeaderCollection() {
                {"X-Requested-With", "XMLHttpRequest"}
            });

        // Tell the mocked context to return the mocked request
        SetupResult.For(mockedhttpContext.Request).Return(mockedHttpRequest);

        mocks.ReplayAll();

        // Set controllerContext
        controller.ControllerContext = new ControllerContext(mockedhttpContext, new RouteData(), controller);
}

J
Jeroen Bernsen

Is AjaxRequest is an extension method. So you can do it the following way using Rhino:

    protected HttpContextBase BuildHttpContextStub(bool isAjaxRequest)
    {
        var httpRequestBase = MockRepository.GenerateStub<HttpRequestBase>();   
        if (isAjaxRequest)
        {
            httpRequestBase.Stub(r => r["X-Requested-With"]).Return("XMLHttpRequest");
        }

        var httpContextBase = MockRepository.GenerateStub<HttpContextBase>();
        httpContextBase.Stub(c => c.Request).Return(httpRequestBase);

        return httpContextBase;
    }

    // Build controller
    ....
    controller.ControllerContext = new ControllerContext(BuildHttpContextStub(true), new RouteData(), controller);

D
Dr.Sai

Looks like you are looking for this,

 var requestMock = new Mock<HttpRequestBase>();
 requestMock.SetupGet(rq => rq["Age"]).Returns("2001");

Usage in Controller :

 public ActionResult Index()
 {
        var age = Request["Age"]; //This will return 2001
 }

h
hkf

You need to mock HttpContextBase and put it into your ControllerContext property, like that:

controller.ControllerContext = 
new ControllerContext(mockedHttpContext, new RouteData(), controller);

and what would mockedHttpContext need to be mocked? tje RequestContext object it requires needs an HttpContextBase() object in the constructor, and HttpContextBase() has no constructor that accepts zero parameters.
I tried: var mocks = new MockRepository(); var mockedhttpContext = mocks.DynamicMock(); var mockedHttpRequest = mocks.DynamicMock(); SetupResult.For(mockedhttpContext.Request).Return(mockedHttpRequest); var controller = new HomeController(Repository, LoginInfoProvider); controller.ControllerContext = new mockedhttpContext, new RouteData(), controller); var result = controller.Index() as ViewResult; However still get the same exception thrown.
Your link doesn't work, but the following seems to work _request.Setup(o => o.Form).Returns(new NameValueCollection());
M
Meer

In current .NET (v 5):

var controller = new SomeController(); // SomeController that inherits Microsoft.AspNetCore.Mvc.ControllerBase
var httpContext = new DefaultHttpContext(); // DefaultHttpContext class is part of Microsoft.AspNetCore.Http namespace
httpContext.Request.Headers.Add("origin", "0.0.0.1"); // Add your custom headers to request
controller.ControllerContext.HttpContext = httpContext;

it works with Microsoft.AspNetCore.Mvc.Core 3.1 as well
B
Bassetassen

To make IsAjaxRequest() to return false during Unit test you need to setup Request Headers as well as request collection value both in your test method as given below:

_request.SetupGet(x => x.Headers).Returns(new System.Net.WebHeaderCollection { { "X-Requested-With", "NotAjaxRequest" } });
_request.SetupGet(x=>x["X-Requested-With"]).Returns("NotAjaxRequest");

The reason for setting up both is hidden in implementation of IsAjaxRequest() which is given below:

public static bool IsAjaxRequest(this HttpRequestBase request)<br/>
{ 
    if (request == null)
    {
        throw new ArgumentNullException("request");
    }
    return ((request["X-Requested-With"] == "XMLHttpRequest") || ((request.Headers != null) && (request.Headers["X-Requested-With"] == "XMLHttpRequest")));
}

It uses both request Collection and header this is why we need to create setup for both Header and Request Collection.

this will make the request to return false when it is not a ajax request. to make it return true you can do the following:

_httpContext.SetupGet(x => x.Request["X-Requested-With"]).Returns("XMLHttpRequest");

N
Niraj Trivedi

I found other way to add a HttpRequestMessage object into your request during Web API as follow

[Test]
public void TestMethod()
{
    var controllerContext = new HttpControllerContext();
    var request = new HttpRequestMessage();
    request.Headers.Add("TestHeader", "TestHeader");
    controllerContext.Request = request;
    _controller.ControllerContext = controllerContext;

    var result = _controller.YourAPIMethod();
    //Your assertion
}

S
Stu

(A bit late to the party but I went for a different route so thought I'd share)

To go for a pure code/mocking way of testing this without creating mocks for Http classes I implemented an IControllerHelper which has an Initialise method which takes the Request as a parameter and then exposed properties I want eg:

    public interface IControllerHelper
    {
        void Initialise(HttpRequest request);
        string HostAddress { get; }
    }

    public class ControllerHelper : IControllerHelper
    {
        private HttpRequest _request;
        
        public void Initialise(HttpRequest request)
        {
            _request = request;
        }

        public string HostAddress =>  _request.GetUri().GetLeftPart(UriPartial.Authority);
    }

Then in my controller I call initialise at the start of the method:

        _controllerHelper.Initialise(Request);

And then my code is only dependent on mockable dependacies.

        return Created(new Uri($"{_controllerHelper.HostName}/api/MyEndpoint/{result.id}"), result);

For functional tests I just override the iControllerHelper in the composition for a substitute.