Jak mockować żądanie na kontrolerze w ASP.Net MVC?

171

Mam kontroler w C # przy użyciu struktury ASP.Net MVC

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

Dostałem kilka wskazówek na temat kpiny i miałem nadzieję przetestować kod za pomocą poniższych i 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);

Jednak ciągle otrzymuję ten błąd:

Wyjątek System.ArgumentNullException: System.ArgumentNullException: Wartość nie może mieć wartości NULL. Nazwa parametru: żądanie w System.Web.Mvc.AjaxRequestExtensions.IsAjaxRequest (żądanie HttpRequestBase)

Ponieważ Requestobiekt na kontrolerze nie ma ustawiacza. Próbowałem, aby ten test działał poprawnie, używając zalecanego kodu z odpowiedzi poniżej.

Użyłem Moq zamiast RhinoMocks, a używając Moq używam następujących dla tego samego testu:

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);

ale pojawia się następujący błąd:

Exception System.ArgumentException: System.ArgumentException: Nieprawidłowa konfiguracja na elemencie, którego nie można zastąpić: x => x.Headers ["X-Requested-With"] at Moq.Mock.ThrowIfCantOverride (Konfiguracja wyrażenia, MethodInfo methodInfo)

Wygląda na to, że nie mogę ustawić nagłówka żądania. Jak ustawić tę wartość w RhinoMocks lub Moq?

Nissan
źródło
Zamień Request.IsAjaxRequest na Request.IsAjaxRequest ()
eu-ge-ne
Mock Request.Headers ["X-Requested-With"] lub Request ["X-Requested-With"] zamiast Request.IsAjaxRequest (). Zaktualizowałem moje pytanie
eu-ge-ne
spróbuj tego
danfromisrael

Odpowiedzi:

212

Korzystanie z 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);

AKTUALIZACJA:

Mock Request.Headers["X-Requested-With"]lub Request["X-Requested-With"]zamiast Request.IsAjaxRequest().

eu-ge-ne
źródło
1
Otrzymuję komunikat „Argument typu dla metody 'ISetupGetter <T, TProperty> Moq.Mock <T> .SetupGet <Tpropert> .... nie można wywnioskować z uage. Spróbuj jawnie określić argumenty typu. Jaki typ ustawić „var request =” do choćby, aby to zadziałało?
Nissan
Właśnie zaktualizowałem moją odpowiedź - nie Request.IsAjaxRequest, ale Request.IsAjaxRequest (). Zaktualizuj też swoje pytanie
eu-ge-ne
Nadal generuje: Wyjątek System.ArgumentException: System.ArgumentException: Nieprawidłowa konfiguracja na elemencie, którego nie można zastąpić: x => x.IsAjaxRequest () at Moq.Mock.ThrowIfCantOverride (konfiguracja wyrażenia, MethodInfo methodInfo)
Nissan
Problem w tym, że IsAjaxRequest () jest statyczną metodą rozszerzającą i nie można jej wyszydzać - zaktualizowałem moją odpowiedź.
eu-ge-ne
powinien być context.SetupGet (x => x.Request) .Returns (request.Object); w powyższym kodzie brakuje znaku „s” na Return nadal skutkuje również wyjątkiem System.ArgumentException: System.ArgumentException: Nieprawidłowa konfiguracja na elemencie, którego nie można zastąpić: x => x.Headers ["X-Requested-With"] o Moq Komunikat o błędzie .Mock.ThrowIfCantOverride (konfiguracja wyrażenia, MethodInfo methodInfo)
Nissan
17

Dla każdego, kto używa NSubstitute, mogłem zmodyfikować powyższe odpowiedzi i zrobić coś takiego ... (gdzie Szczegóły to nazwa metody akcji na kontrolerze)

 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);
sjmarsh
źródło
13

Oto działające rozwiązanie wykorzystujące RhinoMocks. Oparłem to na rozwiązaniu Moq, które znalazłem na 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);
}
Phil Hale
źródło
5

Czy AjaxRequest jest metodą rozszerzającą. Więc możesz to zrobić w następujący sposób za pomocą 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);
Jeroen Bernsen
źródło
4

Wygląda na to, że tego szukasz,

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

Użycie w kontrolerze:

 public ActionResult Index()
 {
        var age = Request["Age"]; //This will return 2001
 }
Dr Sai
źródło
3

Musisz mockować HttpContextBase i umieścić go we właściwości ControllerContext, na przykład:

controller.ControllerContext = 
new ControllerContext(mockedHttpContext, new RouteData(), controller);
Michał Chaniewski
źródło
i co mogłoby zostać wyszydzone z mockedHttpContext? tje Obiekt RequestContext, którego wymaga, wymaga obiektu HttpContextBase () w konstruktorze, a HttpContextBase () nie ma konstruktora, który akceptuje zero parametrów.
Nissan
Próbowałem: var mocks = new MockRepository (); var mockedhttpContext = mocks.DynamicMock <HttpContextBase> (); var mockedHttpRequest = mocks.DynamicMock <HttpRequestBase> (); SetupResult.For (mockedhttpContext.Request) .Return (mockedHttpRequest); kontroler var = new HomeController (Repository, LoginInfoProvider); controller.ControllerContext = new mockedhttpContext, new RouteData (), controller); var result = controller.Index () as ViewResult; Jednak nadal jest zgłaszany ten sam wyjątek.
Nissan
Twój link nie działa, ale wygląda na to, że działa _request.Setup (o => o.Form) .Returns (new NameValueCollection ());
Vdex
2

Aby IsAjaxRequest()zwrócić false podczas testu jednostkowego, musisz skonfigurować nagłówki żądań, a także wartość zbierania żądań zarówno w swojej metodzie testowej, jak podano poniżej:

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

Powód skonfigurowania obu jest ukryty w implementacji IsAjaxRequest (), która jest podana poniżej:

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")));
}

Używa zarówno zbierania żądań, jak i nagłówka, dlatego musimy utworzyć konfigurację zarówno dla nagłówka, jak i zbierania żądań.

spowoduje to, że żądanie zwróci wartość false, jeśli nie jest to żądanie AJAX. aby to zwróciło prawdę, możesz wykonać następujące czynności:

_httpContext.SetupGet(x => x.Request["X-Requested-With"]).Returns("XMLHttpRequest");
Sharad Rastogi
źródło
0

Znalazłem inny sposób dodania obiektu HttpRequestMessage do twojego żądania podczas interfejsu API sieci Web w następujący sposób

[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
}
Niraj Trivedi
źródło
0

(Trochę spóźniony na imprezę, ale poszedłem inną trasą, więc pomyślałem, że się podzielę)

Aby przejść do czystego kodu / mockowania sposobu testowania tego bez tworzenia mocków dla klas HTTP, zaimplementowałem IControllerHelper, który ma metodę Initialise, która przyjmuje Request jako parametr, a następnie ujawnia właściwości, które chcę, np:

    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);
    }

Następnie w moim kontrolerze wywołuję inicjalizację na początku metody:

        _controllerHelper.Initialise(Request);

A potem mój kod jest zależny tylko od mockable zależności.

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

W przypadku testów funkcjonalnych po prostu nadpisuję iControllerHelper w kompozycji na substytut.

Stu
źródło