Unit testing code behinds in ASP.Net Web Forms
13 June, 2010 § 7 Comments
Have you ever inherited an ASP.Net website with waaaaaaay too much logic in the code behinds?
A while back I came up with a solution to solving this problem. I don’t have a name for it, but if you do, please leave a comment at the end of this post.
The problem that I was trying to solve is that I wanted to make a new page and implement it using Test Driven Development. The hard part was that there was a lot of code that basically dealt with the visual aspect and interaction of the page but not much with the data model. I needed to find a way to write tests around this code, as the data model code already had a framework for writing tests.
Here is an example of the before-case:
public class CodeBehind : System.Web.UI.Page { protected void btnFindUser_Click(Object obj, EventArgs e) { if (Page.IsValid) { var dbConnection = new DatabaseConnection(); var users = dbConnection.GetUsers( txtQuery.Text ); if ( !users.empty() ) { dataTable.Bind( users ); } } } }
What I came up with was an introduction of a “controller” class that lived outside of the ASP.Net Web Forms project. Here is an example of the after-case:
// CodeBehind.aspx.cs public class CodeBehind : System.Web.UI.Page { protected void btnFindUser_Click(Object obj, EventArgs e) { controller.FindUser( Page.IsValid, txtQuery.Text, dataTable ); } } // CodeBehindController.cs public class CodeBehindController { public void FindUser( bool pageIsValid, string query, DataTable dataTable ) { if ( pageIsValid ) { var dbConnection = new DatabaseConnection(); var users = dbConnection.GetUsers( query ); if ( !users.empty() ) { dataTable.Bind( users ); } } } } // CodeBehindControllerTests.cs public class CodeBehindControllerTests { public void FindUser_WithNoUsersFound_DataTableIsEmpty() { DataTable table = new DataTable(); CodeBehindController controller = new CodeBehindController(); controller.FindUser( true, "ImpossibleUserToFind", table ); Assert.IsTrue( table.empty() ); } }
With that, I can now mock out the DatabaseConnection and the DataTable and I’m good to go. By passing in Page.IsValid instead of the whole Page object, I’m able to provide types that are easier to instantiate and I also now have code with less dependencies.
Getting this to work is actually very simple. Just introduce another class and put all of your logic in that class. There shouldn’t be a single conditional in your *.aspx.cs page. As long as you follow this pattern, it should be pretty easy to TDD your next ASP.Net Web Forms page.
Any questions? Leave a comment and I’d be glad to help.