Unit Testing ASP.NET Article

Article by Artem Smirnov creator of Ivonna

Background
Many developers recently discovered that the Test Driven Development (TDD) process improves the development process significantly. While writing tests is relatively easy in the desktop world, Web applications are not so easily tested. One of the problems is that a Web application runs in a different process, thus being isolated from the test code. As a result, what we are able to test is a raw HTML output, rather than the application’s internals. Another problem is that it is very difficult to put a Web application into a special environment that is more suitable for testing. For example, we cannot check the behavior of the system when a database is unavailable unless we manually stop the database server. Compare this to the desktop unit testing, where we can easily mock the database connection, telling the system to throw an exception without actually trying to connect.

Several Asp.NET frameworks exist, but they are either parsing the HTML output and recreating the structure of the page (NUnitAsp, now discontinued), or test the client-side functionality (Selenium and WatiN). Another framework, Plazma, hosts the Asp.Net runtime so that it is executed in the test runner process; however, due to the runtime’s inherent untestability, we are still unable to test, let alone mock, the page’s intrinsics. As a result, we can write integration, but not unit, tests. For example, to verify that a certain label displays the user’s name, we have to add a user to our database, navigate to the login page, enter the user’s credentials, navigate to the page being tested, parse the output in order to find a span with a certain ID, and check the span’s inner text. In addition, we have to manually return the database to the previous state.

Frustrated by these complexities, and driven by the common idea that testability equals good code (and vice versa), developers often tend to push as much code from the code-behind files as they can into the presentation logic library, coupling it too much to the view (for example, letting the Presenter handle control events and manipulate controls’ properties). The remaining View is left untested, just because it’s not worth the effort.

It is clear that developers need to be able to access and manipulate various server-side intrinsics, such as the current HttpContext, the page being requested and its controls, the Session etc. You often want to verify that certain events are being fired in a certain order with certain arguments, or that your custom control is initialized properly at the Init stage. Even more important, sometimes you need to test your page in isolation, so you need to be able to mock the rest of your application. On the other hand, you need some browser-side functionality as well: setting up cookies and checking the return status, filling up form values and pushing buttons etc. This is where the Ivonna framework can help.

Testing with the Ivonna framework is completely different from the client-side frameworks. You are programming in a way similar to desktop testing. You set up your mocks, for example, mock the Membership service, then you request a page (you have a “real” instance of the Page class, not the raw Html), find the label you need and check its Text property. But there’s more: you can execute your asserts not only against the response but also during the page’s lifecycle. For example, you can add a Page_Init handler and verify the label’s initial value or visibility. You can also run tests against concrete page classes (for example, if you want to test a page-specific method) or App_Code classes.

A simple example

We’ll start with a simple task. Let’s create a page with a textbox (“NameTextBox”), a button (“SayButton”), and a label (“MessageLabel”). Let’s test the following scenario: if the textbox contains “John”, and the button is clicked, the label should display “Hi John!”.

Note that this is an integration test, and we could do the same, or similar, with any other Web testing framework. To demonstrate Ivonna’s unit testing capabilities, we’ll aim to refactor our code into a View and a Presenter, writing tests for the former (unit testing the Presenter is not a problem since the class resides in a separate assembly). In particular, we’ll write a test for our View that whenever the button is pressed, a certain event is raised. Also, we’ll test a View’s method and verify that it displays the message on the page. This is, to our knowledge, impossible with any other testing framework.

Setup

The setup process is described in the online documentation. First, you have to ensure that TypeMock Isolator and Ivonna is installed. You can download Isolator here and Ivonna here. If you want to use an external test runner, you should enable Isolator before you launch the runner (see this link for details). Next, we create a Web site and a test project. We add references to System.Web, MbUnit, TypeMock Isolator, and Ivonna to our test project. Finally, we set the output of our test project to our Web’s bin folder.

The first test

The first, integration, the test is simple: request a page, enter “John” in the textbox, click the button, and check the label text.

ASP.NET Code Example VB.NET

 

ASP.NET Code Example C#

Although this looks similar to a typical Windows Forms test, there’s something really unusual going on. The first request happens on line 6 (see the VB version), but after it’s finished, the Page instance is still alive. Not only we can inspect its properties and controls, we can even set some properties and post the values back, “clicking” a button or a similar control. This is possible because the “server” and the “client” code live in the same process.

But wait, there’s more. As you will see in the next section, not only you can execute test code before or after a request (or, as in our first test, between the requests). You can execute it during the request, attached to one of the page’s lifecycle events.

Back to our test, in order to make it pass, you just handle the button click event:

But then, you might discover that this code is a mix of presentation logic and view concerns, and might want to refactor it into View and Presenter classes. We’ll follow the classic MVP pattern, only without the Model part. The View doesn’t hold a reference to the Presenter, but rather raises the appropriate events, to which the Presenter is subscribed. The Presenter then invokes the appropriate method on the View, modifying the output. For example, the View raises the SayHi event in response to the button click, and has the ShowMessage method that modifies the text of the label.

Following the tradition, we’ll define the IView interface as follows:

Testing the View events

Our next task is to verify that our View raises the SayHi event in response to the button click.

There are a number of steps we should perform in order to verify that the SayHi event is raised. We should subscribe to this event somehow, which means we should have a reference to its source, the page. This should happen after the page is created, but before the postback event is raised, so we have to execute this code during the request. We can achieve our goal adding a handler to the page’s Init event. In other words, since we can’t just subscribe to the SayHi eventbefore the postback (the sender doesn’t exist yet), we have to do it during the postback, and the best way is to handle the Init (or Load) event, which Ivonna can help you with. This becomes a two-step process:

  1. Subscribe to the Init event using Ivonna’s infrastructure.
  2. In the Init event handler, subscribe to the SayHi event. The page instance is the sender parameter of the Init handler, and we should cast it to IView.

While the IView interface is defined in the App_Code folder, we can use it if we reference the App_Code assembly in the precompiled Web’s bin folder. Typically, you would want to put it into a separate assembly.

Let’s see the test code:

Code Sample ASP.NET – VB.NET

Code Sample ASP.NET – C#

First, we create a TestSession instance, as we do in almost every Ivonna test. Then, we have to retrieve the page we are going to post. At line 13 (C# version), we create an instance of EventCather. This is a helper class designed to verify event raising. Next, we create a WebRequest instance. This is different from our previous test, where we used session.GetPage(), but this syntax allows us to modify the request before processing. At line 17, we add a delegate to the page’s Init event (VB doesn’t support anonymous delegates, so we have to create a separate Sub). Note that we can do it before the page itself is created: the delegate is stored in another object and attached after the page is created. The delegate casts the page to IView and attaches another delegate to the SayHi event. After we have prepared everything, we execute the postback at line 23 and verify that the SayHi event has been raised at line 24.

In order to make the test pass, we have to add two modifications to out page. We have to implement IView, since the test raises an InvalidCastException exception at line 18. And we have to raise the SayHi event in the SayButton_Click method. But making the test pass is beyond the scope of this article.

 

Testing the View methods

In response to events raised by the View, the Presenter invokes various methods that change the View’s appearance. In our case, the Presenter should instruct the View to display the message. It is up to the View to decide how the message is displayed. We already know that the message should appear as the text of MessageLabel. So, our purpose is to verify that invoking the ShowMessage method changes the MessageLabel’s text appropriately.

This method is invoked during the page’s life cycle, and is better tested using the Prerender even handler, similar to the previous test. However, we can simplify things a little and invoke the method after the request, which is executed in order to obtain the page object:

Code Sample ASP.NET – VB.NET

ASP.NET Code Sample – C#

 

Conclusion

Ivonna makes possible to write both integration and unit tests. In fact, combined with the power of TypeMock Isolator, you can write functional tests for any part of your application: mock the external dependencies to test your system, mock the repositories to test the MVP part, etc. Developers are not forced to move all code from the page being tested to an external library; instead, you are free to start with integration tests and refactor gradually, or start with unit testing the page and add integration tests later.

While Ivonna does not enforce design decisions, it does encourage some practices that makes writing the tests simpler, such as coding to an interface, or following the Single Responsibility Principle.

Article by  Artem Smirnov creator of Ivonna

Start with Isolator now

Learn more on ASP.NET unit testing