3.2. Unit Testing ASP.NET WebForms

An ASP.NET application is made up of two main parts. The aspx file contains the ASP.NET markup, which is a mixture of HTML, ASP.NET controls, and ASPX tags that contain code to be executed on the server before being returned to the user. The second part is the aspx.cs file that is the code-behind for a particular page. The code-behind files contain C# code to support the main ASP.NET page, allowing you to hook into the page lifecycle and execute code on the server when the page loads, or more commonly when a user requests some data that needs to be processed by the server.

However, because these code-behind files hook into the lifecycle of the page they have a tight dependency to the core ASP.net runtime. When it comes to testing, this tight dependency causes a huge amount of problems. If you attempt to access the object outside of the ASP.net runtime, such as when writing unit tests then you will receive a number of error messages in and around the core engine. When it comes to the aspx file, any of the code placed in the view is not accessible from another class — as such you wouldn't be able to write any unit tests for it.

In order to be able to unit test ASP.NET WebForms you need to think about how to develop the application in a different way. The most important thing to remember is to keep the view and the code-behind classes as thin as possible. They should contain the minimum amount of code to function and as soon as the code starts to become complex it should be moved to a separate isolated class. We feel that the way Visual Studio and ASP.NET WebForms behave by default encourages you to store a large amount of logic within your code-behind file; however, this is not the correct place for complex logic. If you think back to Chapter 2 and SOLID, The Single Responsibility Principle is broken by code-behind files.

ASP.NET also doesn't help to keep the view lightweight as it encourages controls such as the GridView control. An example of the GridView is next; the control populates the grid and provides built in paging support:

<asp:GridView ID="GridView1" runat="server" AllowPaging="True"
    AutoGenerateColumns="False" DataSourceID="ObjectDataSource1">
    <Columns>
        <asp:CommandField ShowSelectButton="True" />
        <asp:BoundField DataField="Name" HeaderText="Name"
                        SortExpression="Name" />
    </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
                      SelectMethod="GetData"
                      TypeName="WebApplication1.ObjectSourceBusinessObject">
</asp:ObjectDataSource>

This kind of code litters your aspx files while also sending over-complex HTML down to the user. This doesn't mean that it needs testing as it's a standard control, but it does mean that there is not a clean separation and no possible way to write unit tests. Even automate UI tests (discussed in Chapter 5) would be difficult.

One way to help encourage you to keep your view and code-behind files clean is to follow the Model-View-Presenter (MVP) design pattern. A design pattern describes some experience or knowledge gained allowing this to be shared with other people. The idea is to allow people to take one person's experience, use the pattern to understand the concept in a high level, and then apply the techniques to their own scenario. Having a pattern in this high-level format allows you to discuss the concepts in the same way, leaving low-level implementation details out of the discussion. The advantage is that the discussion can focus on the technical aspects, without focusing on someone's specific problems. There are many different patterns, and MVP is just one of them.

When it comes to ASP.NET and MVP, the aim is to move all of the normal logic you would find in a code-behind file into a separate class. In order for this class to communicate with the view, the view implements a particular interface defining the view's behavior. The view is then injected into the controller so that it can take advantage of the functionality the view offers. In this example, the view will have two elements, one element is a button and the other is a ListBox. When the user clicks the button, it will request data from the server and populate the result in the ListBox.

If you look at the implementation, you'll see that the view will need to implement an interface. In this example, you need the view to be able to fire an event in order to request data, have a property for the data to be stored and a method to cause the UI to update and display the data:

public interface IDisplayDataView
{
    event EventHandler DataRequested;
    List<string> Data { get; set; }
    void Bind();
}

After the view has implemented the interface, it can pass the instance of itself into a controller, in this case the DisplayDataController:

public partial class _Default : System.Web.UI.Page, IDisplayDataView
{
    private DisplayDataController Controller;
    protected void Page_Load(object sender, EventArgs e)
    {
        Controller = new DisplayDataController(this);
    }
}

The controller stores the view instance for future methods calls. Within the constructor, the controller also needs to hook up any events provided by the UI and attach them to the appropriate method to ensure that the functionality is met:

public class DisplayDataController
{
    public IDisplayDataView View { get; set; }

    public DisplayDataController(IDisplayDataView view)
    {
        View = view;
        View.DataRequested += GetData;
    }
}

Because this code is in an isolated class away from the ASP.NET lifecycle, you can write a test to verify that the functionality is correct. In this case, you are verifying that an event handler is attached to the DataRequested event, but you don't care which type of handler. After constructing the controller, you verify that this has happened:

[TestFixture]
public class DisplayDataControllerTests
{
    [Test]
    public void Ctor_should_hook_up_events()
    {
        IDisplayDataView view = MockRepository.GenerateMock<IDisplayDataView>();
        view.Expect(v => v.DataRequested += null).IgnoreArguments();

        new DisplayDataController(view);

        view.VerifyAllExpectations();
    }
}

With the event in place, the next step is to implement the GetData method, which will be called when the event is raised. Again, as you have abstracted away from the ASP.NET code-behind file, you can write the code test-first. The idea is that when GetData is called, it should populate the Data property on the view. To simulate the view, you can use Rhino Mocks to produce a stub object. The controller will then interact with the stub and the Data property as if it was a real page. By having the IDisplayDataView in place you have the control and flexibility to be able to test. If the controller does not populate the data property after a call to GetData, then the test will fail:

[Test]
public void GetData_should_populate_data()
{
    IDisplayDataView view = MockRepository.GenerateStub<IDisplayDataView>();
    DisplayDataController controller = new DisplayDataController(view);
    controller.GetData(this, EventArgs.Empty);

    Assert.AreEqual(4, view.Data.Count);
}

After the data has been set, the view needs to be alerted in order to refresh the UI and display the data to the user. There are many ways this notification could be made, in this example you expect that the controller will call the Bind method on the view.

In our controller test, you can use a mock object to verify that the call is made correctly after GetData is called:

[Test]
public void GetData_should_call_Bind()
{
    IDisplayDataView view = MockRepository.GenerateMock<IDisplayDataView>();
    view.Expect(v => v.Bind());
    DisplayDataController controller = new DisplayDataController(view);
    controller.GetData(this, EventArgs.Empty);

    view.VerifyAllExpectations();
}

The results of the implementation via the tests would be next:

public void GetData(object sender, EventArgs e)
{
    View.Data = new List<string> { "String A", "String B", "String C",
                                   "String D" };
    View.Bind();
}

While MVP is a useful pattern to consider when developing WebForms applications and can definitively improve testability, it's still not perfect. The problem is that the ASP.net application still needs all the events to go via the code-behind file. So, you still have methods containing logic that is unable to be tested. In this example, there are the following two methods that are untested as a result:

public void Bind()
{
    ListBox1.DataSource = Data;
    ListBox1.DataBind();
}
protected void Button1_Click(object sender, EventArgs e)
{
    if (DataRequested != null)
        DataRequested(sender, e);
}

While two methods won't cause major problems, you can imagine that as the application grows, this untested code will also increase, which might result in more bugs being introduced into the code base. For example, when writing this example you had 100 percent passing tests, but because you didn't call ListBox1.DataBind(), nothing was displayed on the UI.

MVP also doesn't provide much support when attempting to move code from the view into testable classes. While it's possible, it can increase the complexity of the application.

The use of MVP pattern was never a mainstream approach to developing ASP.NET WebForms. People wrote code in the code-behind file and also wanted to be able to unit test it. This became a major issue for Microsoft with developers starting to get frustrated with the lack of testability when compared with other open source frameworks. These open source frameworks offered huge advantages to developers over WebForms, with many people taking advantage and using these frameworks. As a result, Microsoft released a new framework based on the MVC pattern called ASP.NET MVC. This was a huge jump forward in terms of testability support as it solves some of the issues we mentioned with WebForms and the MVP pattern. ASP.NET MVC is the main framework covered in this chapter.

3.2.1. ASP.NET MVC

Due to the problems people encountered with WebForms, Microsoft set their sights on developing a web framework based on the MVC pattern. Since WebForms was released, many other web frameworks such as Ruby on Rails, Django (a web framework for the Python language), and MonoRail (a .NET web framework) have huge main impacts, all with the common theme of being extremely testable and easy to maintain. With pressure from the community, Microsoft released ASP.NET MVC.

ASP.NET approaches the problem of a web framework in a similar manner to the frameworks mentioned previously, focusing on allowing developers to create a testable, decoupled system. This is not meant as a replacement for WebForms, but as an alternative offering for web developers to use. I can imagine some people staying with WebForms and continuing to develop software using that approach, but others will pick up the new framework and run with it.

By having a decoupled web framework you interchange sections of the framework and provide your own. For example, there are now a number of options in terms of the view engine, the component that renders the HTML. Previously, it was extremely difficult to decouple the WebForms view engine from the ASP.NET core engine itself, which limited your choice to WebForms. With the new architecture, you have much more flexibility and choice. Although there are many different view engines available, there appear to be three main engines: NVelocity, Brail, and Spark. NVelocity is a port from the Apache Jakarta Velocity project. It first provided support for MonoRail as a view engine and now supports ASP.NET MVC. MonoRail and the Castle team have since forked the project and have continued to provide functionality and bug fixes. NVelocity aims to provide a similar and cleaner approach to writing your view template, also known as the page that will be converted into HTML.

Brail and Spark are two other view engines available. Similar to NVelocity, Brail also was designed for MonoRail. The aim of Brail is to allow you to take advantage of the Boo language. Boo is a "wrist-friendly language for the Common Language Infrastructure (CLI) aimed at cutting down the amount of code you need to write. With Brail, you can use the Boo language within your View template. Spark allows you to include code snippets within strings within HTML elements. This improves readability and allows you to bind data elements to HTML very simply.

All the view engines take a different approach and are aligned with the different ways people enjoy writing software. The approach you take is a personal choice. In the following example you will use WebForms.

If you wish to learn more about the ASP.NET MVC Framework, check out Professional ASP.NET MVC 1.0 by Rob Conery, Scott Hanselman, Phil Haack, and Scott Guthrie; published by Wrox 2009.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.59.173.242