0

I have a web api 2 web service get method. Inside I'm using HttpContext.Current.Request.UserHostAddress. When calling my controller method directly int he unit test this isn't filled in so is errors with null object. So I searched for how to fill this in and found the following which helped with that issue: Add IP address to HttpRequestMessage

However, this needs a server name to send the request to. The problem is that when tests run the VSExpress will need to be running for this API web service, which it won't be when just running the tests. On top of that even if it was it seems it picks a random port to run on so I couldn't hardcode the address like he does in the above link. How can I test my api 2 method given the above issues?

This is the line that blows up when I just test the api method

string ip = HttpContext.Current.Request.UserHostAddress;

[EDIT] Answer

Just so everyone knows here is the solution in code

public class MyController : ApiController
{
    private: HttpRequestBase httpRequest;

    public MyController()
    {
        httpRequest = new HttpRequestWrapper(HttpContext.Current.Request)
    }

    public MyController(HttpRequestBase http)
    {
        httpRequest = http;
    }

    public HttpResponseMessage Get()
    {
        string ip = httpRequest.UserHostAddress;
    }
}

I use Moq in the unit test:

Mock<HttpRequestBase> httpRequestMock = new Mock<HttpRequestBase>();

httpRequestMock.Setup(x => x.UserHostAddress).Returns("127.0.0.1");

// then pass httpRequestMock.Object to my controller ctor and good to go
Community
  • 1
  • 1
user441521
  • 6,942
  • 23
  • 88
  • 160
  • 1
    If you are open to changing your code - just make "give me user IP" functionality to be dependency of your class (i.e. pass interface like `IGiveMeRequestInformation` to constructor of controller if you use some DI framework) and mock it for tests. – Alexei Levenkov Sep 19 '14 at 15:35

3 Answers3

4

Decouple your controller from the HTTP context. There might be some built-in functionality to do this with which I'm unfamiliar, but one approach would be to simply inject a mockable object. Consider something like this:

public interface IRequestInformation
{
    string UserHostAddress { get; }
}

public class RequestInformation : IRequestInformation
{
    public string UserHostAddress
    {
        get { return HttpContext.Current.Request.UserHostAddress; }
    }
}

Now you've abstracted the dependency on HttpContext behind an interface. If you're using dependency injection, inject that interface into your controller. If you're not, you can fake it:

// in your controller...
private IRequestInformation _request;
public IRequestInformation RequestInfo
{
    get
    {
        if (_request == null)
            _request = new RequestInformation();
        return _request;
    }
    set { _request = value; }
}

Then use that in your controller logic:

string ip = RequestInfo.UserHostAddress;

Now in your unit tests you can supply a mock/fake/etc. for the RequestInfo property. Either create one manually or use a mocking library. If you create one manually, that's simple enough:

public class RequestInformationFake : IRequestInformation
{
    public string UserHostAddress
    {
        get { return "some known value"; }
    }
}

Then just supply that to the controller when arranging the test:

var controller = new YourController();
controller.RequestInformation = new RequestInformationFake();
// run your test
Nicklas Møller Jepsen
  • 1,248
  • 2
  • 16
  • 34
David
  • 208,112
  • 36
  • 198
  • 279
1

Replace your references to HttpContext by references to HttpContextBase. When in your code, initialize the HttpContextBase with a HttpContextWrapper instance, which is a the default behavior implementation in a web stack.

However in your test inject a custom HttpContextBase implementation where you implement the methods and behaviors needed by your test only.

As precised in the link:

The HttpContextBase class is an abstract class that contains the same members as the HttpContext class. The HttpContextBase class enables you to create derived classes that are like the HttpContext class, but that you can customize and that work outside the ASP.NET pipeline. When you perform unit testing, you typically use a derived class to implement members with customized behavior that fulfills the scenario you are testing.

samy
  • 14,832
  • 2
  • 54
  • 82
  • I like this idea as it's less code but I can't get this to compile. I create a var in my method: private HttpContextBase httpContext; and then in my prod controller I try to set it to the current but it gives compile error that it can't convert which seems odd as I should be able to store a child object in a base reference I would think. Do I have to manually convert this or will that cause issues that I'm not aware of? The line that fails is httpContext = HttpContext.Current; – user441521 Sep 19 '14 at 15:45
  • HttpContext doesn't inherit from HttpContextBase, so you must use `httpContext = new HttpContextWrapper(HttpContext.Current)`. Ideally, you would event inject these values with a IOC framework, but you can always initialize the context at construction time – samy Sep 19 '14 at 15:46
  • Oh that's an actual .NET class. Sounded more like a class you wanted me to make. Strange that those don't relate to each other given their names. Thanks. – user441521 Sep 19 '14 at 15:48
  • Yup, actual class since .net 3.5 : http://msdn.microsoft.com/en-us/library/system.web.httpcontextwrapper%28v=vs.110%29.aspx – samy Sep 19 '14 at 15:48
0

Add the following method to the controller, or inject the equivalent. It uses the magic string MS_HttpContext because that's what the AspNetWebStack implementation uses for exactly the same purpose.

HttpContextBase HttpContextBase => HttpContext.Current != null
    ? new HttpContextWrapper(HttpContext.Current)
    : (HttpContextBase)Request.Properties["MS_HttpContext"]

Replace all other uses of HttpContext.Current in the controller with HttpContextBase.

When unit testing:

var context = new Mock<HttpContextBase>();
...

controller.Request = new HttpRequestMessage();
controller.Request.Properties["MS_HttpContext"] = context.Object;
Aaron Queenan
  • 851
  • 8
  • 14