Design for Testability in .NET Frameworks


Design for Testability in .NET Frameworks


ASP.NET WEB Forms framework not developed with Testability in mind
  • One of the main intent of ASP.NET was to allow the developer focus on Business logic and abstracts the typical client side web elements like HTML, JavaScript and CSS.

  • Server controls allow developer to quickly and effectively arrange views. 
  • ASP.NET uses object-oriented model and event-oriented model. Page framework creates an object model based on the server control tags in the page and also fires a series of events and the handlers in code behind files gets executed.



  • By this some sort of separation of concerns is accomplished by separating UI rendering and event handling code(Code Behind Model and Server Controls).
  • Page framework renders the Controls object model into HTML tags with CSS formatting and Javascript code.
  • But if we see the dependencies on System.Web.UI.Page class, we can figure out that code-behind code is tightly coupled with ASP.NET runtime and to unit test a method in “code behind” we might require ASP.NET runtime. Mocking runtime is not allowed too. So unit testing methods in “code behind” is pretty hard. We might end-up doing integration testing even to get feedback about small changes which we make in the code behind.



  • Any code placed in a code-behind class has direct access to the HTTP context and can read and write cookies, posted data, query string parameters, session state, HTTP headers, and so forth. 

  • Code-behind classes during HTTP GET requests, setup the page to display and for HTTP POST requests, orchestrates the back-end process workflow, receives response from back-end and prepare the next view for the user.

  •  If the code behind has significant level of complexity with workflow to be orchestrated, the only way to validate the code is through integration testing(We need to trigger the UI).

  • ASP.NET Web Forms was successful in making the Web application behave very similar to desktop applications with concepts like Postback, ViewState, AJAX etc.But doesn't have proper separation of concerns to enhance testability.

ASP.NET MVC framework and Testability


  • When software is broken down into small parts and each part has a well-defined single responsibility and if each part is cohesive and loosely coupled, testability improves.

  •  If its easier to write unit tests for any part of the software, then development or maintenance  becomes faster and cheaper. 

  • MVC was designed from the ground up to be testable. Design choices were made to allow developers to create testable applications.
  • MVC Framework implements the MVC pattern, basically considered as an UI pattern. Major components being a) Model b) View c) Controller.

  •  The Controller which contains the logic related to flow of the application. UI code resides in View. Business logic (related to Business activities and processes) resides in the Model(Model in turn can consume back-end services or other BLL assemblies). This separation of concern improves testability and maintainability.

  • A Controller can be tested same as a simple POCO (Plain Old CLR Object). And Model classes are in fact POCO objects.

  • ASP.NET MVC Framework has most of its functionality as plug-gable and by that providing orthogonality to the framework. Developer can swap the default Controller factory with a custom controller factory(for dependency injection etc), developer even can easily replace (with mocks for testing) static helper classes like HttpContext and HttpRequest if required. These static helper classes are very well abstracted from the developer, in most cases we might not need to create instances of static helper classes.
  • The UI rendering engine (Razor) and the Routing Engine (that parses the URL and invokes the Handler) are not tied with MVC framework. They can be used in any .NET applications.
  • One of the main design principle of MVC being "Convention over Configuration". The user customized extensible parts of the framework(Controller, Views, Models etc) are automatically picked up by the framework based on their naming conventions and location inside the project structure. They need not be configured. But rules such as format of the URL must be configured so that Routing engine can extract data, name of the handler that it needs to invoke etc.


Entity Framework and Testability

  • In most of the applications, the Business Objects/Domain Objects Model doesn't will not be in synch with Data base schema (Logical Model of database).

    In most of the applications, the Business Objects/Domain Objects Model doesn't represent the Data base schema (Logical Model of database).

    Main driving force for Database schema is avoiding redundancy of data by normalization. A normalized database enhances the performance of data access and also ensures integrity and consistency of data.

    Main objective of Business Objectives/Domain Objectives is to allow re-usability, maintainability , easy extensibility of application logic by deriving Object Model from real world tangible objects in the problem domain. (Considering a Business Software Application is to automate an existing manual process).

    Data members in Business Objects  will not be normalized and data will be duplicated in different Domain Objects/Business objects.

    There is always a chance that data in one database table distributed in multiple in-memory Business Objects/Domain Objects.

    Data in a Domain Object/Business Object might also be split into multiple tables.

  • Entity frameworks major objective is to bridge the gap between the Business Objects( Conceptual data model) and Database Schema(Logical Model).
  •  EF abstracts the Logical database schema from the developer, so that developer need not write boiler plate code to access database and map the data retrieved into the BOs or DOs.
  •  Core of EF is Models. An Entity Framework Data Model contains 1) Conceptual model 2) Mapping Model 3) Logical Model.
  • Logical model(Schema definition of database) is hidden from the programmer, and Entities defined in the conceptual model will be used for database access and management.
  • Once the Conceptual Model, Mapping Model and Logical Model are defined, programmer need not retrieve the data in the same structure how data is stored in the database and feed them into Business Objects or Domain Objects. 
Testability Of EF
  • The big disadvantage of EF is rigid architecture, where it’s very difficult to mock some components of the framework.
  • In the database first approach, the default code generated by the code generation tools from the model contains ObjectContext class(which takes care of pulling data in and out of the database. It also keeps track of all changes during a business transaction. And the Entitiy classes that are generated are derived from EntityObject.
  • In the code first approach, the important classes are DbContext(wrapper around ObjectContext) and DbSet.DbSet represents an entity set. We will have one DbSet for each type in business /domain model. That entity can be an aggregate root of the domain. 

  •  ObjectContext, DbContext, DbSet, EntityObject being concrete classes, it’s difficult to mock them. 
  •  The other major disadvantage is the upper layers(BLL or UI) are aware of EF. It would be difficult to swap EF with other ORM in data access layer. 

  • So to make the code testable and to make the upper layers unaware of EF being used, EF code should be wrapped and to upper layers only interfaces to access the domain objects should be exposed.

  • ObjectContext/DbContext are by themselves implementation of UnitOfWork pattern. DbSet, Entityobject are by themselves implementation of Repository pattern. But since they are concrete classes, they are tied to EF. 

  • If we want to replace EF DbContext with some other data store context : ActiveDirectoryContext etc, it would be difficult to switch.
  • The solution would be to wrap ObjectContext/DbContext (EF specific Unit Of Work) in a generic UnitOfWork. Wrap the ObjectSet/DbSet (EF repositories) in generic Repository. Make the Domain Objects Persitent ignorant by allowing them to be just POCO Objects. 
  • T4 code generation templates to generate Repository & UnitOfWork patterns and testable POCO entity objects can be found

No comments:

Post a Comment