My current project based in Asp .net makes considerable use of Http handlers to process various requests? So, is there any way by which I can test开发者_StackOverflow社区 the functionality of each of the handlers using unit test cases? We are using Nunit and Moq framework to facilitate unit testing.
I think these blog entries from a while back are relevant:
http://www.kongsli.net/nblog/2009/05/03/aspnet-35-improving-testability-with-systemwebabstractions/
http://www.kongsli.net/nblog/2009/05/28/testability-with-systemwebabstractions-and-no-mock-framework/
See example #2 in the first post for an example on how to unit test an HttpHandler.
If you dont care about unit tests and want something quick and dirty you can use Fiddler
if you want a more integrated approach (Unit testing) you can use the WebRequest and WebResponse.
You sure can, although I haven't done myself "in anger".
Use a System.Net.WebClient to make HTTP calls against your handlers, and evaluate what comes back, that will allow you to test the public facing interface of the handler.
In this example I've hard-coded my target, and I'm using a method on the WebClient that will return a string.
The WebClient also gives you access to the ResponseHeaders, Encoding and other useful 'webby' stuff; you can also upload info as well.
using System.Net;
namespace UnitTestHttpHandler
{
public class TestHarness
{
public static string GetString()
{
WebClient myWebClient = new WebClient();
return myWebClient.DownloadString("http://localhost/Morphfolia.Web/ContentList.ashx");
}
}
}
You can then use the TestHarness to call the target HttpHandler and verify the results in your tests (or use a better approach to your testing if you know one - I'm not a unit testing guru).
[TestMethod]
public void TestMethod1()
{
string x = UnitTestHttpHandler.TestHarness.GetString();
Assert.IsTrue(x.Length > 5);
}
The default interface for IHttpHandler
is not testable because the param for ProcessRequest(HttpContext context)
is not mockable.
This answer is inspired by this post.
To make your IHttpHandler
implementation testable you must first make a small change so we can use the mockable HttpContextBase
:
From:
class YourHttpHandler : IHttpHandler
{
public bool IsReusable => true;
public void ProcessRequest(HttpContext context)
{
/* Your handler implementation */
}
}
To:
class YourHttpHandler : IHttpHandler
{
public bool IsReusable => true;
public void ProcessRequest(HttpContext context)
=> ProcessRequest(new HttpContextWrapper(context));
public virtual void ProcessRequest(HttpContextBase context)
{
/* Your handler implementation */
}
}
Your functionality needs to be moved from ProcessRequest(HttpContext context)
to ProcessRequest(HttpContextBase context)
.
The ProcessRequest(HttpContextBase context)
method can now be called in your tests with a mock object to verify the functionality within.
It's useful the create a helper class that can be used to quickly instantiate YourHttpHandler
, send a mocked request, and get access to the response.
public class YourHttpHandlerTester : YourHttpHandler
{
public class Response
{
public HttpResponseBase HttpResponse { get; }
public string Body { get; }
public Response(HttpResponseBase httpResponse, string body)
{
HttpResponseBase = httpResponse;
Body = body;
}
}
public Response ProcessRequest(HttpRequestBase httpRequest)
{
var memoryStream = new MemoryStream();
var httpResponse = CreateHttpResponse(memoryStream);
var httpContext = CreateHttpContext(httpRequest, httpResponse);
base.ProcessRequest(httpContext);
var response = CreateResponse(httpResponse, memoryStream);
return response;
}
protected virtual HttpResponseBase CreateHttpResponse(MemoryStream memoryStream)
{
var httpResponseBaseMock = new Moq.Mock<HttpResponseBase>();
httpResponseBaseMock.Setup(x => x.OutputStream).Returns(memoryStream);
return httpResponseBaseMock.Object;
}
protected virtual HttpContextBase CreateHttpContext(HttpRequestBase httpRequest, HttpResponseBase httpResponse)
{
var httpContextBaseMock = new Moq.Mock<HttpContextBase>();
httpContextBaseMock.Setup(x => x.Request).Returns(httpRequest);
httpContextBaseMock.Setup(x => x.Response).Returns(httpResponse);
return httpContextBaseMock.Object;
}
protected virtual Response CreateResponse(HttpResponseBase httpResponse, MemoryStream memoryStream)
{
memoryStream.Position = 0;
var body = new StreamReader(memoryStream).ReadToEnd();
var response = new Response(httpResponse, body);
return response;
}
}
This class can be used to quickly create readable tests:
[Fact]
public void Test()
{
var httpHandler = new YourHttpHandlerTester();
var request = CreateHttpRequestFromString("test");
var response = testHandler.ProcessRequest(request);
Assert.NotEmpty(response.Body);
}
public HttpRequestBase CreateHttpRequestFromString(string body)
{
var httpRequestBaseMock = new Moq.Mock<HttpRequestBase>();
var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(body)))
httpRequestBaseMock.Setup(x => x.InputStream).Returns(stream);
return httpRequestBaseMock.Object;
}
YourHttpHandlerTester
has plenty of virtual methods that can be overridden as necessary.
Likewise, the Response
class can be improved so that it exposes methods from HttpResponseBase
like the http status code, headers, and anything else you may want to check.
You can do INTEGRATION testing of the handler using the methods mentioned in the other answers, to do UNIT testing you will need to create some interfaces and extract the core functionality out of the handler, as well as create some mock objects.
You won't be able to unit test ALL parts of it because it relies upon outside resources (those you'll be mocking) - but that's fine, thats why we HAVE integration testing.
If you want to test the communication between your handlers and the Web UI then yes, integration testing is the way to go for that. In order to unit test your logic, could you not instead separate your business logic into other classes (I'd use a separate assembly for the business layer) and mock / unit test these classes instead outside of your presentation layer?
Once you have a structured (and unit tested) business layer that has been separated from the presentation layer your handlers can simply instantiate your concretes and invoke the provided methods. Once this is done, you can then move onto integration testing as your business logic will have been unit tested.
精彩评论