I'm looking to integration test a web service built on ASP.NET Web API 2. Many things such as cookies, getting the current principal, etc. are done through HttpContext.Current.
I found the following resource on integration testing ASP.NET:
http://amy.palamounta.in/blog/2013/08/04/integration-testing-for-asp-dot-net-web-api/
This works great. It spins up an in-memory host and combined with the automated schema generation of Entity Framework 6, setup and teardown are easy.
The problem comes in when things try and use HttpContext.Current to, as aforementioned, get cookies/etc. It seems that when hosting in-memory using HttpServer, HttpContext.Current is always null. This does make a little sense given there's no real request, but is a pain - it means the integration tests can't cover anything requiring anything from this property.
What can I do here? It looks like very little of the data I'm using is present anywhere but HttpContext.Current, so am I just going to have to spin up full external instances on IIS?
In order to test your api using self host you have to avoid calling HttpContext.Current from a web api context. You can wrap it in something you can mock, like so:
public static class CurrentHttpContext
{
public static Func<HttpContextBase> Instance = () => new HttpContextWrapper(HttpContext.Current);
}
Then when you are creating your test server you could set the CurrentHttpContext to some fake HttpContextBase. For example using FakeItEasy (http://fakeiteasy.github.io/):
var context = A.Fake<HttpContextBase>();
CurrentHttpContext.Instance = () => context;
And then use this anywhere you need to access HttpContext:
var context = CurrentHttpContext.Instance();
// do what you need to do to httpcontext now like setting principal, cookies etc
Related
For example, in a ASP.NET page you would do something like
Cache.Add({...}) and access it via Cache["key"]. In this context, Cache is the System.Web.Caching.Cache object.
Is there anyway to do this type of ASP.NET application level caching in web API controllers?
Take a look at the MemoryCache class. From its MSDN documentation:
The MemoryCache class is similar to the ASP.NET Cache class. The
MemoryCache class has many properties and methods for accessing the
cache that will be familiar to you if you have used the ASP.NET Cache
class. The main differences between the Cache and MemoryCache classes
are that the MemoryCache class has been changed to make it usable by
.NET Framework applications that are not ASP.NET applications.
You can create a new instance of a MemoryCache yourself, or you can use the default AppDomain-wide instance via the MemoryCache.Default static property.
Edit: You'll need to add a reference to System.Runtime.Caching.dll if you wish to use this type.
If you are web hosting, why not?
var context = HttpContext.Current;
if (context != null)
{
if (context.Cache["g"] == null)
{
context.Cache["g"] = 9.81;
}
}
But you are adding a dependency on ASP.NET by doing so. Even though ASP.NET Web API has ASP.NET in the name, the Web API is host-agnostic. That is, ASP.NET/IIS is not the only hosting option; the Web API can be self-hosted as well. Something for you to consider before going down that route.
You need to type
HttpContext.Current.Cache
to access the instance. There is no Cache property declared at the Controller level, like on a Page.
Note that the context that hosts the API will need to support caching.
If you are referring to Output caching in ASP.NET Web API. Take a look at this project,
https://github.com/filipw/AspNetWebApi-OutputCache
We've got a ServiceStack 3.9.x service that we're trying to unit test end-to-end (via an in-process service host and accessing it via C# native clients), and we're running into a snag where it seems that the session is not accessible via the means we typically use to access it when running this way.
We typically access the current session (using servicestack's built-in system and custom AuthSession and providers, which all works fine running in IIS against an AppHost derived from AppHostBase) using this:
EndpointHost.AppHost.TryResolve<ICacheClient>().SessionAs<SsoUserSession>();
However, when trying to access this in unit testing (against an AppHost derived from AppHostHttpListenerBase), we get an exception thrown trying to get at the session: "Only ASP.NET Requests accessible via Singletons are supported" which appears to be a hard-coded error in the SessionFeature.
So the question is this: can one access sessions via the cache provider when unit testing via a service host derived from AppHostHttpListenerBase? And if so, how?
I've run into this before too.
The way I handle it is create create something like an extension method to get the session from the cache client that just calls the base, but before calling the base..check the IoC container for the session first. If it's there, just use that instead, and I just inject the session I want to use when testing.
This is used somewhere in servicestack but I can't seem to find the method that does it...anyway an extension method could look something like this
public static MyTypedSession GetMyTypedSession(this ICacheClient cache)
{
var typedSession = ServiceStackHost.Instance.Container.TryResolve<MyTypedSession>();
if (typedSession != default(MyTypedSession))
return typedSession;
return cache.SessionAs<MyTypedSession>();
}
Then instead of calling SessionAs in your code to get the typed session, you would just call GetMyTypedSession, and it would work fine for testing, so long as you Register your fake MyTypedSession
Here's some c# psuedo test method
public void SomeTestMethod()
{
var session = new MyTypedSession { IsAuthenticated = true; };
//get your container and register the session
container.Register(session);
var someValue = TestCodeThatUsesASession();
Assert(someValue);
}
I'm unsure what kinda delay looking in the IoC container everytime you need a session is though.
Sorta strange to add that code just for testing but oh well, works for me and save me time :).
I'm developing a Web API RESTful service on the Azure platform.
I thought that the default client-side caching behavior would be to cache GET requests (since GET is idempotent and all).
To my surprise when I deployed the service to Azure all responses were sent with a Cache-Control: private header or other cache-disallowing header.
I tried the solution suggested in this question, it did work locally in IIS but did not work once we deployed to Azure. I could not find anything in the documentation about this ability which I thought was very basic in a RESTful service, I really hope that I'm missing something obvious, in MVC it was very easy.
tl;dr
We need to cache GET requests on the client side when using Azure and Web API.
I don't believe Azure is doing anything to you in this respect. It's a matter of you needing to specify exactly what caching properties you want for your resource(s).
With WebAPI you can control what caching properties your response has via the CacheControlHeaderValue which is accessible via the myHttpResponseMessage.Headers.CacheControl property.
Assuming you had a controller action like this:
public Foo Get(int id)
{
Foo myFoo = LoadSomeFooById(id);
return myFoo;
}
You'll need to do something like this to take control of the caching explicitly:
public HttpResponseMessage Get(int id)
{
Foo myFoo = LoadSomeFooById(id);
HttpResponseMessage myHttpResponseMessage = this.Request.CreateResponse(HttpStatusCode.OK, myFoo)
CacheControlHeaderValue cacheControlHeaderValue = new CacheControlHeaderValue();
cacheControlHeaderValue.Public = true;
cacheControlHeaderValue.MaxAge = TimeSpan.FromMinutes(30);
myHttpResponseMessage.Headers.CacheControl = cacheControlHeaderValue;
return myHttpResponseMessage;
}
Many of the other properties related to caching that you'd expect are also available on the CacheControlHeaderValue class, this is just the most basic example.
Also, bear in mind my example is extremely brute force/simplistic in that all the caching behavior/logic is right there in the action method. A much cleaner implementation might be to have an ActionFilterAttribute which contains all the caching logic based on attribute settings and applies it to the HttpResponseMessage. Then you could revert to the more model centric action method signature because you would, in this case, no longer need access to the HttpResponseMessage anymore at that level. As usual, many ways to skin the cat and you have to determine which works best for your specific problem domain.
Take a look at this http://forums.asp.net/post/4939481.aspx it implements caching as an attribute that modifies the HTTP response.
Disclaimer: I haven't tried it.
I would recommend this https://github.com/filipw/AspNetWebApi-OutputCache
Simple, quick and has various options to cache.
Hope that helps
I want to confirm that my "HomeController" class is being selected by the route I've created. So I have a test like this:
[TestMethod]
[UrlToTest("http://localhost:14478/home")]
[HostType("ASP.NET")]
[AspNetDevelopmentServerHost("$(SolutionDir)\\MvcBuildApp")]
public void MyTest()
{
System.Diagnostics.Debugger.Break();
RouteCollection routes = new RouteCollection();
MvcApplication.RegisterRoutes(routes);
MvcApplication.RegisterGlobalFilters(GlobalFilters.Filters);
//This fetches DefaultControllerFactory (for now, anyway...)
var factory = ControllerBuilder.Current.GetControllerFactory();
//mock a context...
var httpContext = CreateHttpContext("http://localhost:14478/home", "GET");
RouteData route = routes.GetRouteData(httpContext);
var ctrlInstance = factory.CreateController(new RequestContext(httpContext, route), (string)route.Values["controller"]);
//ASSERT the instance is of type "HomeController"
//...
}
It fails, saying that 'http://localhost:14478/home' completed successfully without running the test.
I noticed that in the VS output window, there is also this message No connection could be made because the target machine actively refused it 127.0.0.1:14478. I figured Cassini must not be active. So I chose to launch the site being tested (ctrl+F5) before launching the unit test. It then changed the VS output to this:
WebTestAdapter.ConnectToHostAdapter: unexpected exception occured.
Microsoft.VisualStudio.TestTools.HostAdapters.AbortTestExecutionException:
Error in the application. at
Microsoft.VisualStudio.TestTools.HostAdapters.WebTestAdapter.ConnectToHostAdapter()
To try to resolve this, I've followed the advice of these articles:
Debug while running a test in ASP.NET solution
Unit Tests for ASP.NET web services: this discusses the "completed successfully without running a test" error.
Configuring ASP.NET Unit Tests: I got the $(SolutionDir) idea from this article.
...but I still get the error no matter what I do. Suggestions?
UPDATE/CLARIFICATION
This question is not about testing MVC routes. The purpose of this question is to discover how to make ASP.NET MVC properly initialized to allow more "in depth" automated testing. I have chosen a "route testing" scenario, against the DefaultControllerFactory, merely as an example. In this example, the DefaultControllerFactory does not behave properly unless ASP.NET MVC is properly initialized.
This is a common requirement when testing MVC applications, and lucky you, there is a framework that will simplify your life when testing MVC controllers:
The tools is:
http://mvccontrib.codeplex.com/
And as an example you can create tests with just one line:
"~/".ShouldMapTo<HomeController>(controller => controller.Index());
As a reference:
http://geekswithblogs.net/thomasweller/archive/2009/11/02/unit-testing-asp.net-mvc-routes.aspx
http://www.arrangeactassert.com/how-to-unit-test-asp-net-mvc-controllers/
You may be interested in reviewing Steve Sandersons MVC testing framework. If you download the source you will have a great example on how to initialize the MVC framwork:
http://blog.codeville.net/2009/06/11/integration-testing-your-aspnet-mvc-application/
I am very new to web service stuff so please be kind.
I have written a simple POJO class, and deployed it on an axis2 server:
public class Database {
private Project project;
public void login(){
project = new Project();
project.setDescription("Hello there");
project.setName("To me");
}
public Project getProject(){
return project;
}
}
I call the service from a c# client:
localhost.Database db = new WindowsFormsApplication1.localhost.Database();
db.login();
localhost.getProjectResponse pr = new WindowsFormsApplication1.localhost.getProjectResponse();
pr = db.getProject();
When I debug the response is null.
At the java end, when I call getProject, the project object is null.
What's happening?
How do I preserve the state of project between service calls?
For most toolkits, web services are stateless by default. I think axis is no different.
If you want to maintain state between calls then you will need to enable sessions. An example on how to maintain sessions in axis can be found at:
http://kickjava.com/src/test/session/TestSimpleSession.java.htm
On the .NET side you will need to assign a CookieContainer to your request to store the session identifier. See HOW TO: Use CookieContainer to Maintain a State in Web Services for more information.
I think your code would look something like this:
localhost.Database db = new WindowsFormsApplication1.localhost.Database();
// Assign the CookieContainer to the proxy class.
db.CookieContainer = new System.Net.CookieContainer();
db.login();
localhost.getProjectResponse pr = new WindowsFormsApplication1.localhost.getProjectResponse();
pr.CookieContainer = db.CookieContainer;
pr = db.getProject();
I think that should let you do what you want -- but I wouldn't recommend it.
Designing service interfaces is a bit different than designing object oriented interfaces. Service interfaces typically eschew the use of state and instead require the consumer to provide all of the relevant information in the request.
From Service-Oriented Architecture:
Services should be independent,
self-contained requests, which do not
require information or state from one
request to another when implemented.
I would definitely recommend reading that article and perhaps revisiting your design.
I'm not sure why #shivaspk left a comment instead of writing an answer, it is quite correct: web service calls (not just axis calls) are meant to be stateless, so although the project object gets created by
db.login();
when you call
db.getProject();
It is being called on a different instance of your Database class that was created by Axis to service the second call.
There is no really good answer to your question, except for you to rethink what you are trying to do. If you need some kind of authentication (via login), then that authentication needs to be part of every web service call.