17

In an ASP.NET Core 1.0 (MVC 6) project I have a Controller action method in which I use the Response property to set a header:

[HttpGet]
public IActionResult Get()
{
    ...

    Response.Headers.Add("Location", location);

    ...
}

I tried to implement a unit test for this action method, but the value of the Response property is null.

This was easy to solve in the previous versions of ASP.NET, because the Response property had a setter, and you could simply set its value to a new HttpResponse instance during a unit test.

But in ASP.NET 5 Response does not have a setter, only a getter.
How can I set a value for Response in a unit test?

UPDATE: Just to make clear: this question is about ASP.NET Core 1.0. The other question linked as duplicate is about ASP.NET 4, and the Api changed since then so the answer there does not apply to this question.

Mark Vincze
  • 7,737
  • 8
  • 42
  • 81
  • I'm aware of and familiar with mocking. The problem here is that I can't find a way with the API of ASP.NET to assign a mock object to the `Response` property. – Mark Vincze Feb 10 '16 at 15:42
  • 1
    You need to mock the whole context, and within that mock the response and its headers collection. Then use this to populate the controller's ControllerContext (which is where the controller gets its Request and Response properties). – Richard Feb 10 '16 at 15:51
  • Yes, that used to be the case in MVC 5, but now in MVC 6 the `ControllerContext` does not exist any more. – Mark Vincze Feb 10 '16 at 15:57
  • 1
    Doesn't the `ControllerBase` class contain a [`ControllerContext`](https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs#L108) with a setter? You can set the `ControllerContext` to a mocked one with a mock HttpContext, which in turn has a mock Response etc... If you are not inheriting from ControllerBase, you won't even have a property `Response` (unless you add it yourself) – Daniel J.G. Feb 10 '16 at 16:49
  • Daniel: that was the case in ASP.NET 4, but in ASP.NET 5 (or ASP.NET Core 1) there is no `ControllerBase` class and `ControllerContext` property. – Mark Vincze Feb 10 '16 at 21:58
  • @MarkVincze in my comment you have a link to the ControllerBase class in the officiak ASP MVC Core repository! Whether you decide to create a controller that inherits from it or not is up to you. (But if you don't, just make sure you write your controller class in a testable way, like injecting an IHttpContextAccessor) – Daniel J.G. Feb 10 '16 at 22:33
  • Daniel that link points to the dev branch of the aspnet repository, that class is not part of neither the master branch, neither the package distributed as a Nuget package. In the current version of ASP.NET, the base class is `Microsoft.AspNet.Mvc.Controller`, and that does not have the `ControllerContext` property. – Mark Vincze Feb 10 '16 at 23:01
  • I see, in RC1 they are called [`Controller`](https://github.com/aspnet/Mvc/blob/master/src/Microsoft.AspNet.Mvc.ViewFeatures/Controller.cs#L111) and `ActionContext`. I basically wanted to clarify that as in MVC 5, in MVC Core there is a base class with a way of setting the HttpContext and Request properties (although indirectly through another property). I would start making my mind about the upcoming changes though, [`ControllerBase` and `ControllerContext`](https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs#L108) :) – Daniel J.G. Feb 10 '16 at 23:59
  • Cool, thanks for the info! :) In the meantime I posted an answer with a working solution, I guess in the new version simply the `ActionContext` will have to be replaced by using the `ControllerContext`. – Mark Vincze Feb 11 '16 at 09:16

2 Answers2

23

I found one solution to the problem, however, it's a bit tedious and convoluted, so I'm still interested in seeing a simpler approach, if any exists.

The Controller gets the value of its Response property from ActionContext.HttpContext. What makes mocking this difficult is that all these properties are read-only, so we cannot just simply set a mock value, we have to create mocks for every object in play.

The part of the Response I needed in my test was the Headers collection, so I had to create and use the following mocks to make that availale. (Mocking is done with Moq.)

var sut = new MyController();

// The HeaderDictionary is needed for adding HTTP headers to the response.
// This needs a couple of different Mocks, because many properties in the class model are read-only.
var headerDictionary = new HeaderDictionary();
var response = new Mock<HttpResponse>();
response.SetupGet(r => r.Headers).Returns(headerDictionary);

var httpContext = new Mock<HttpContext>();
httpContext.SetupGet(a => a.Response).Returns(response.Object);

sut.ActionContext = new ActionContext()
{
    HttpContext = httpContext.Object
};

This is a bit more code than what I'd like to see to mock a single property, but I couldn't find any better approach yet, and it seems to be working nicely.

Mark Vincze
  • 7,737
  • 8
  • 42
  • 81
  • To have less code, you should check NSubstitute (http://nsubstitute.github.io/) – efaruk Feb 12 '16 at 07:24
  • Efaruk, I'm using Moq as a mocking framework. What exactly would NSubstitute provide which Moq is not capable of? (Also, does NSubstitute already support CoreCLR?) – Mark Vincze Feb 12 '16 at 08:44
  • 1
    @MarkVincze Did you ever find a more elegant solution to this? – Shawn Mclean Nov 08 '16 at 17:34
  • Hey @ShawnMclean, no I haven't found any nicer solution, I'm still using this—pretty verbose—approach. – Mark Vincze Nov 09 '16 at 10:09
  • 15
    In case anyone is still looking at this. `ActionContext` doesn't exist in `Microsoft.AspNetCore.Mvc.Controller`. It has been replaced by `ControllerContext` reference: http://stackoverflow.com/questions/37286530/actioncontext-gone-in-microsoft-aspnetcore-mvc-controller – GreenyMcDuff Apr 25 '17 at 14:07
  • 1
    You could throw this in an extension method `public static T WithControllerContext(this T controller) where T : Controller {/*...*/}` so you can re-use it only when needed `controller = new MyController(/*...*/).WithControllerContext();`. – Aaron Wynn Jul 10 '17 at 15:20
  • To have less code, there is no need to mock HttpContext and HttpContext.Response. Just create a new DefaultHttpContext object, which will have a non-null Response object with non-null Headers property. – robbie fan Aug 12 '22 at 09:28
0

You can use a ResponseWrapper with virtual properties and you can mock it and expect one call for virtual AddHeader method with "Location" parameter. Relevant Question Here: MOQ - Mocking MVC Controller's Response.Cookies.Clear()

You can check those examples: for ASP.Net 5 MVC 6:

Another option if you want to try NancyFx(http://nancyfx.org/) (https://github.com/NancyFx/Nancy) it's quite easy: https://github.com/NancyFx/Nancy/wiki/Testing-your-application

For example - if you were testing a login page and wanted to verify that the user was redirected to an “error” page if they entered incorrect credentials you could use the ShouldHaveRedirectedTo assertion:

[Fact] public void Should_redirect_to_login_with_error_details_incorrect() {
    // Given
    var bootstrapper = new DefaultNancyBootstrapper();
    var browser = new Browser(bootstrapper);

    // When
    var response = browser.Post("/login/", (with) => {
        with.HttpRequest();
        with.FormValue("Username", "username");
        with.FormValue("Password", "wrongpassword");
    });

    // Then
    response.ShouldHaveRedirectedTo("/login?error=true&username=username");

}

There is more:

[Fact]
public void Should_display_error_message_when_error_passed()
{
    // Given
    var bootstrapper = new DefaultNancyBootstrapper();
    var browser = new Browser(bootstrapper);

    // When
    var response = browser.Get("/login", (with) => {
        with.HttpRequest();
        with.Query("error", "true");
    });

    // Then
    response.Body["#errorBox"]
            .ShouldExistOnce()
            .And.ShouldBeOfClass("floatingError")
            .And.ShouldContain(
                "invalid",
                StringComparison.InvariantCultureIgnoreCase);
}
Community
  • 1
  • 1
efaruk
  • 882
  • 9
  • 25
  • Yep, I could create an abstraction just for adding a header to the response, but it feels like an overkill, so I would like to avoid that if possible. Thanks for the example, but I want to use MVC, so the NancyFX stuff doesn't apply. – Mark Vincze Feb 10 '16 at 15:51
  • Also, the example you sent is using the `ControllerContext` property, which does not exist any more in MVC 6. I expect this to be possible to be done with MVC 6, just the properties I need to use were probably moved around a little bit. – Mark Vincze Feb 10 '16 at 15:54
  • I have edited my answer and also you have ActionContext for replacement... – efaruk Feb 10 '16 at 16:02
  • I checked the links you sent, but they don't have information about mocking the `Response` property. The other link in the answer is for ASP.NET 4. I'll try mocking with the `ActionContext`, but that won't be straightforward either, since the `Response` property doesn't have a setter there either. – Mark Vincze Feb 10 '16 at 22:03
  • You don't need setter; HttpResponseBase has virtual properties you can create a Wrapper class and you can mock HttpResponseBase: `public class ResponseWrapper { private readonly HttpResponseBase _response; public ResponseWrapper(HttpResponseBase response) { _response = response; } }` You can check for mocking in mvc: http://stackoverflow.com/questions/18534953/nsubstitute-mocking-the-request-response-object-inside-a-mvc-web-api-controlle – efaruk Feb 11 '16 at 15:06
  • Efaruk I don't see how that `ResponseWrapper` would make the situation any easier. The difficulty is mocking the properties of `Controller`, not the `HttpResponse`. – Mark Vincze Feb 11 '16 at 15:54
  • There is no easier way for your choices @MarkVincze!!! Did you gave my answer down point !? – efaruk Feb 12 '16 at 07:23