Skip to main content

Testable WinForms Applications (MVP pattern)

Today, WinForms apps mainly belong to legacy code because of increasing popularity of WPF. And when one team decides about the development stack for the brand new desktop application, they mainly vote for WPF. On the other hand, there are demands for WinForms applications when it is needed to upgrade existing software which is tightly coupled with WinForms, demands for higher performance, etc.

Different Approaches

There are many opinions on the subject about testable WinForms apps. Some claim that it's not possible to test WinForms apps because there is a lot of dependency between user events and business logic.

Standard Windows Form contains Designer.cs partial class and a partial class which contains event handlers for user actions. So, if we follow this same principle and try to implement app logic in event handlers, we can conclude that it would be very hard to write tests for this kind of application. But, if we look at this problem from some distance, we can conclude that every application with user interface could be represented as an interaction between three main components: Data, User Interface and Business Logic.

This is the basic idea of MV* patterns. If we succeed to segregate these three components, then our application is on a good way to be testable. I made a simple WinForms app using MVP pattern. The entire project could be fetched from github.

In this post, I would just use code snippets to give a general idea of how the code looks like.

But First, Few Words About MVP Pattern

MVP stands for Model-View-Presenter. Model is a component that contains data. It is just a data holder for our forms. View represents the User Interface. It contains design description of our form. Presenter is a component that does most of the job in our WinForms app. It is subscribed to view events and those events are results of user interaction with our form (clicks on buttons, text change, selection change, etc.) and OS interaction with our form (load, show, paint, etc.). Presenter needs to handle all these events and, after their processing, make appropriate action on the view.

Code Structure

Let's start with our view component.

public interface IProductView
{
     event EventHandler ViewLoad;
     event EventHandler<ProductViewModel> AddNewProduct;
     event EventHandler<ProductViewModel> ModifyProduct;
     event EventHandler<int> DeleteProduct;
     event EventHandler<int> ProductSelected;

     void PopulateDataGridView(IList<Product> products);
     void ClearInputControls();
     void ShowMessage(string message);
}

public partial class Products : Form, IProductView
{
    ...
}
We created an interface IProductView that defines rules how Presenter and User Interface component will interact. Now, let's take a look at our presenter component.

public class ProductPresenter
{
    private IProductView view;
    private IProductDataAccess dataAccesService;
    
    public ProductPresenter(IProductView view, IProductDataAccess dataAccesService)
    {
        this.view = view;
        this.dataAccesService = dataAccesService;
        SubsribeToViewEvents();
    }

    private void SubsribeToViewEvents()
    {
        view.ViewLoad += View_Load;
        view.AddNewProduct += View_AddNewProduct;
        view.ProductSelected += View_ProductSelected;
        view.ModifyProduct += View_ModifyProduct;
        view.DeleteProduct += View_DeleteProduct;
    }
    ...
}
Our presenter takes IProductView interface in its constructor. By this way, our view (form) can easily be replaced with another view which implements the same interface. Also, when it comes to mocking, we can easily inject this dependency through constructor. Another component is IProductDataAccess which represents database interface.

public interface IProductDataAccess
{
    IList<Product> GetAllProducts();
    Product GetProduct(int id);
    bool AddProduct(Product product);
    bool DeleteProduct(int productId);
    bool EditProduct(int productId, Product product);

    string ErrorMessage { get; }
}

One Test Case Example

This test case shows how we can easily mock external dependencies in Presenter component.


 [Test]
 public void ExpectToCallAddProductOnAppropriateEventReceived()
 {
     IProductView view = Substitute.For<IProductView>();
     IProductDataAccess dataAccess = Substitute.For<IProductDataAccess>();
     ProductPresenter presenter = new ProductPresenter(view, dataAccess);
           
     ProductViewModel viewModel = new ProductViewModel()
     {
         NameText = "Test",
         PriceText = "2"
     };
           
     view.AddNewProduct += 
          Raise.Event<EventHandler<ProductViewModel>>(view, viewModel);
     dataAccess.Received().AddProduct(Arg.Is<Product>
                           (x=>x.Price == 2 && x.Name == "Test"));
 }

Conclusion

This is just a simple example of how powerful an architecture that starts with an abstraction can be. It is always better to start with some components on the higher level and then, defining the interfaces, you define rules how those components will interact with each other.

Comments

Popular posts from this blog

Design Patterns: Singleton

Tyipically the first design pattern most people learn, often wrongly ☺ To give an introduction, we can say that singleton is one of the creational design patterns which ensures only one class instance with single point of access thru entire application.  Because it is relatively simple to implement, the Singleton pattern is sometimes misapplied in situations where it is not the most suitable choice. When to use it? Here are the few examples of corrent usage of singleton: Configuration Management  Centralized configuration settings for consistent use thru entire application Caching Maintaning  Single istance of cached objects for easy and fast acces Logging  Ensure unified mechanism to avoid duplication of log files, formats, etc Global State Management  Centralized management of the state which is needed to be shared accross the application Resource sharing  Thread pools, database connection, I/O operations When not to use it? On the other hand, here are fe...

Design Patterns: Builder

This is also, like a Singleton , one of the creational design patterns. It provides the way of creating complex objects step by step by simple chaining and every particular step is independent of other steps. Let us dive into the real example of usage. For showing purpose we have created an example in C# which creates simple SQL queries using described pattern.  using System; using System.Text; namespace BuilderPatternExample { public interface ISqlQueryBuilder { ISqlQueryBuilder Select(string columns); ISqlQueryBuilder From(string table); ISqlQueryBuilder Where(string condition); ISqlQueryBuilder OrderBy(string columns); string Build(); } public class SelectQueryBuilder : ISqlQueryBuilder { private readonly StringBuilder _queryBuilder; public SelectQueryBuilder() { _queryBuilder = new StringBuilder(); } public ISqlQueryBuilder Select(string columns) { ...

Why Do Employers Lie In Interviews?

This is a very common subject that many of us have already experienced. But when you realize that half of what has been said at interviews is actually a lie, you are already at least six months in the company, you have already started some project and it wouldn’t be appropriate to leave the company at that moment. Why is this happening? First of all, let us see how the usual interview process looks like in software development companies. First round interview in most of these companies is an interview with HR. This is the first insight about the company. A person who works in HR is usually someone who, in most cases, doesn’t understand what the software is and how the software development process goes. Big respect to those companies where HR knows these things. This phase usually contains some standard questions about your personality, what do you like about the company, how this company is something that you are actually looking for, where you see yourself in five/ten  years etc… ...