Wednesday, October 20, 2010

As much as possible out of dependence on HttpContext



In ASP.NET, the natural enemies of unit testing is HttpContext, which is the core of ASP.NET, extremely complex, but can not be Mock1 - shows that Microsoft can write such a huge ASP.NET framework really is not so easy. Now the situation has improved a lot, so you can already use System.Web.Abstractions.dll, and this assembly provides an abstraction for the HttpContext, which is HttpContextBase abstract class. Therefore, ASP.NET MVC, the various components are dependent on HttpContextBase not HttpContext. This is a good practice, since we can get rid of as much as possible of the HttpContext.
But this seems to be a paradox. While already on the HttpContext to Mock (This enhanced testability), but over-reliance on HttpContext for unit testing is also an injury. This is due to HttpContext object nature: it is too complicated. You should have noticed, this is a pet in a set of thousands of objects, from request, response, application, cache ... ... contains a Web application, almost all the information needed. If you want to test a method depends on the HttpContext, you are bound to the Mock HttpContext object for the filling all kinds of information - depending on the complexity of business may be. Moreover, Mock concern is "behavior" that is concerned about the things that used to do a "path." Then if one thing can be done using multiple paths, how will the need to prepare before the test all the paths, and verify that the code being tested, "adopted and used only one path," therefore, Stub slowly coming into line of sight. Stub concern is the "state" ... ... This is another topic, but also involves the use of Record & Replay or Arrange-Act-Assert way to unit test, put aside.

Speaking prior to unit test the view, Old Zhao has been talked about in the view should only use the data ViewData. This is not the first time talking about giving up HttpContext, and since has "abstract" is a powerful weapon, the all "disharmony" factor can be isolated. Just in the MVP mode, View and Presenter are used to interact with their abstract, all Web controls, HttpContext and other objects are gone, we have eyes only for "data" and "model." Similarly, in the ASP.NET MVC in Action approach, should not use the HttpContext, which is based on the good "can test" of the consideration. You may want to, now HttpContextBase object already Mock the ah. Yes, it certainly "can", but doing so will cause the expansion of the unit test code because the test code in a considerable part of the test data must be concerned about the preparation, instead of being tested for functionality. Action for a method, it should be of concern to the user interaction with business logic, rather than "how to HTTP requests into usable data." End of the day, or to "separation of concerns."

In the ASP.NET MVC responsible for "transforming the data" level for the Model Binder. On this point, the existing "sample" most concerned about the Form or QueryString in the data into Action parameters, but where in fact available Model Binder more. For example, in "best practice" code, the original AccountController the Delete method implementation is as follows:

public ActionResult Delete (string userName)
(
this.MiddleTier.UserManager.Delete (userName);

Uri urlReferrer = this.Request.UrlReferrer;
return this.Redirect (urlReferrer.ToString ());
)


After the removal of the specified object, the page will jump to the Url Referrer address. In the above code, this value will be obtained by visiting Request.UrlReferer to. This makes your Action methods and HttpContext produced dependency unit test code so it will need to write this:

[TestMethod]
public void DeleteTest ()
(
string userName = "jeffz";
Uri urlReferrer = new Uri ("http://www.microsoft.com");

var mockHttpContext = new Mock ();
mockHttpContext.Setup (c => c.Request.UrlReferrer). Returns (urlReferrer);

var mockController = this.GetMockController ();
mockController.Setup (c => c.MiddleTier.UserManager.Delete (userName)). Verifiable ();
mockController.Object.ControllerContext = new ControllerContext (
mockHttpContext.Object, new RouteData (), mockController.Object);

mockController.Object.Delete (userName) ...
)


In the unit test code, we had a HttpContextBase Mock object, it's Request.UrlReferrer property returns the object we are ready, and then construct a new ControllerContext and to the Controller. And if we UrlReferrer Delete method can be used as the parameter, then the unit test code is at once much simpler:

[TestMethod ()]
public void DeleteTest ()
(
string userName = "jeffz";
Uri urlReferrer = new Uri ("http://www.microsoft.com");

var mockController = this.GetMockController ();
mockController.Setup (c => c.MiddleTier.UserManager.Delete (userName)). Verifiable ();

mockController.Object.Delete (userName, urlReferrer) ...
)


Some people may ask, not that the properties from the Request for UrlReferrer value you why we construct a ControllerContext, you can not directly set the Controller object such as so much simpler:

mockController.Setup (c => c.Request.UrlReferrer). Returns (urlReferrer);

Seems feasible, but you will find the time to run, the framework will throw an exception, that only members of the interface, or you can override the members can be Mock. Yes, Controller's Request property is not virtual and can not override. Controller class so the design is intentional, the purpose is to limit the available paths. Imagine, if you Mock the Controller.Request property, but by Controller.HttpContext.Request visit code, how to do it well on the way similar to the practice of overloading design.



In general, all of which are several ways to delegate to them the only way, and only that method can be override the. In preparing this test, we have only identified Mock entrance to avoid the test code to understand methods to achieve over the issue.

Back to the topic. If you want the Delete method of access urlReferrer by the parameters, then we have to prepare Model Binder-related components:

public class UrlReferrerModelBinder: IModelBinder
(
public object BindModel (
ControllerContext controllerContext,
ModelBindingContext bindingContext)
(
return controllerContext.HttpContext.Request.UrlReferrer;
))


And it can be directly applied to the parameters of the Action:

public class UrlReferrerAttribute: CustomModelBinderAttribute
(
private static UrlReferrerModelBinder s_modelBinder =
new UrlReferrerModelBinder ();

public override IModelBinder GetBinder ()
(
return s_modelBinder;
)
)


Consequently, we have the Delete method can be written as:

public ActionResult Delete (string userName, Uri urlReferrer)
(
this.MiddleTier.UserManager.Delete (userName);
return this.Redirect (urlReferrer.ToString ());
)


Today's code, whether the application or the framework of the library, must consider "testability"







Recommended links:



Premier Trace And Ping TOOLS



Business STORAGE



OGM converter



2009IT affect China - moved to action



MP4 To Flash



Evaluate Religion



DLL calling convention and name of the MODIFICATION in



Qihoo give the flag To win users for free



How to control DATAWINDOW in PB modify column properties



Webmaster Tips Optimize Network Performance To



Dell is holding a 10 billion U.S. dollars In cash ready to deal



3GP to FLV



Proper way to make money: to be a know how to "lose money," the dealer



Yuan Meng: Ubuntu 9.10 new Version of the debut



Life Difficult For PC Industry In 2009, Will Welcome The First Decline Since 2001



With CloneCD CD-RW disc to save the damaged



No comments:

Post a Comment