Singleton Design Pattern in Automated Testing

Singleton Design Pattern in Automated Testing

Definition

Ensure a class has only one instance and provide a global point of access to it.Instance control– prevents other objects from instantiating their own copies of the Singleton object, ensuring that all objects access the single instance.Flexibility– since the class controls the instantiation process, the class has the flexibility to change the instantiation process.Lazy initialization– defers the object’s creation until it is first used.

UML Class Diagram

classDiagram
    BaseSingleton~T~ <|-- BasePage~TElementMap,TPageValidator~
    BasePage~TElementMap,TPageValidator~ <|-- SearchEngineMainPage
    BasePage~TElementMap,TPageValidator~ o-- BaseElementMap
    class BaseSingleton~T~ {
        +T Instance
    }
    class BasePage~TElementMap,TPageValidator~ {
        +TElementMap Map
        +Navigate(string url)
        +Validate()
    }
    class SearchEngineMainPage {
        +Search(string text)
    }
    class BaseElementMap {
        +IWebDriver Driver
    }

Participants

The classes and objects participating in this pattern are:

  • Page Objects (SearchEngineMainPage)

    Holds the actions that can be performed on the page like Search and Navigate. Exposes an easy access to the Page Validator through the Validate() method. The best implementations of the pattern hide the usage of the Element Map, wrapping it through all action methods.

  • BasePage

    Gives access to the child’s page element map class and defines a standard navigation operation.

  • BasePage

    AAdds an instance to the child page’s validator class through the Validate method.

  • BaseSingleton

    BaseSingleton - This is an abstract class that contains a static property of its child instanc

Create Singleton in BasePage

The most straightforward way to integrate the Singleton Design Pattern in all of your existing page objects is to add a static variable and property in the BasePage class. If you are not familiar what the BasePage classes are, refer to my article- Advanced Page Object Pattern in Automation Testing.

public class BasePage<M>
    where M : BasePageElementMap, new()
{
    private static BasePage<M> instance;

    protected readonly string url;

    public BasePage(string url)
    {
        this.url = url;
    }

    public BasePage()
    {
        this.url = null;
    }

    public static BasePage<M> Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new BasePage<M>();
            }

            return instance;
        }
    }

    protected M Map
    {
        get
        {
            return new M();
        }
    }

    public virtual void Navigate(string part = "")
    {
        Driver.Browser.Navigate().GoToUrl(string.Concat(url, part));
    }
}

The drawback of the above solution is that you cannot reuse the singleton implementation for your Facade classes. The answer to the mentioned problem is to create a base generic singleton class that can be derived from all pages and facades.

Non-thread-safe BaseSingleton Class

Find below, the most trivial implementation of the generic base class for the Singleton Design Pattern.

public abstract class Nonthread-safeBaseSingleton < T >
    where T: new()
    {
    private static T instance;

public static T Instance
{
    get
    {
        if (instance == null)
        {
            instance = new T();
        }

        return instance;
    }
}
}

In most scenarios for automation tests, this solution should be sufficient. However, this implementation is not thread-safe. To be possible to execute tests in parallel, I am going to propose you other variations of the base class that are thread-safe.

For more detailed overview and usage of many more design patterns and best practices in automated testing, check my book “Design Patterns for High-Quality Automated Tests, C# Edition, High-Quality Tests Attributes, and Best Practices”.  You can read part of three of the chapters:

Defining High-Quality Test Attributes for Automated Tests

Benchmarking for Assessing Automated Test Components Performance

Generic Repository Design Pattern- Test Data Preparation

Thread-safe BaseSingleton Class with Lock

public abstract class Thread-safeBaseSingleton < T >
    where T : new()
    {
    private static T instance;
private static readonly object lockObject = new object();

private thread -safeBaseSingleton()
    {
}

public static T Instance
{
    get
    {
        if (instance == null)
        {
            lock (lockObject)
            {
                if (instance == null)
                {
                    instance = new T();
                }
            }
        }
        return instance;
    }
}
}

Here, I am using a lock statement that ensures that one thread does not enter a critical section of code while another thread is in the critical section. If another thread tries to enter a locked code, it will wait, block until the object is released. As you can see the code doesn’t lock the T instance, instead it locks its internal object. This is done to prevent deadlocks.

Thread-safe BaseSingleton Class with Lazy

The built-in .NET framework generic class Lazy saves some code for implementing the lazy initialization. Also, it is thread-safe.

public abstract class ThreadSafeLazyBaseSingleton<T>
    where T : new()
{
    private static readonly Lazy<T> lazy = new Lazy<T>(() => new T());

    public static T Instance
    {
        get
        {
            return lazy.Value;
        }
    }
}

Thread-safe BaseSingleton Class with Nested Classes

The most complicated variation of the base class implementing Singleton Design Pattern uses nested classes and reflection.

public abstract class ThreadSafeNestedContructorsBaseSingleton<T>
{
    public static T Instance
    {
        get
        {
            return SingletonFactory.Instance;
        }
    }

    internal static class SingletonFactory
    {
        internal static T Instance;

        static SingletonFactory()
        {
            CreateInstance(typeof(T));
        }

        public static T CreateInstance(Type type)
        {
            ConstructorInfo[] ctorsPublic = type.GetConstructors(BindingFlags.Instance | BindingFlags.Public);

            if (ctorsPublic.Length > 0)
            {
                throw new Exception(string.Concat(type.FullName, " has one or more public constructors so the property cannot be enforced."));
            }


            ConstructorInfo nonPublicConstructor =
                type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[0], new ParameterModifier[0]);

            if (nonPublicConstructor == null)
            {
                throw new Exception(string.Concat(type.FullName, " does not have a private/protected constructor so the property cannot be enforced."));
            }

            try
            {
                return Instance = (T)nonPublicConstructor.Invoke(new object[0]);
            }
            catch (Exception e)
            {
                throw new Exception(
                    string.Concat("The Singleton could not be constructed. Check if ", type.FullName, " has a default constructor."), e);
            }
        }
    }
}

The nested class contains static constructor to tell the C# compiler not to mark the type as before-field-init. Now the BasePage classes should not have constructors to follow the Singleton Design Pattern, because of that the new() class constraint is removed.

The structure of the base classes for the Page Object Pattern should be slightly changed due to the earlier mentioned reason.

public abstract class BasePageSingletonDerived<S, M> : thread-safeNestedContructorsBaseSingleton<S>
    where M : BasePageElementMap, new()
    where S : BasePageSingletonDerived<S, M>
{
    protected M Map
    {
        get
        {
            return new M();
        }
    }

    public virtual void Navigate(string url = "")
    {
        Driver.Browser.Navigate().GoToUrl(string.Concat(url));
    }
}

public abstract class BasePageSingletonDerived<S, M, V> : BasePageSingletonDerived<S, M>
    where M : BasePageElementMap, new()
    where V : BasePageValidator<M>, new()
    where S : BasePageSingletonDerived<S, M, V>
{
    public V Validate()
    {
        return new V();
    }
}
public class SearchEngineMainPage :
BasePageSingletonDerived<
    SearchEngineMainPage,
    SearchEngineMainPageElementMap,
    SearchEngineMainPageValidator>
{
    private SearchEngineMainPage() { }

    public void Search(string textToType)
    {
        this.Map.SearchBox.Clear();
        this.Map.SearchBox.SendKeys(textToType);
        this.Map.GoButton.Click();
    }

    public override void Navigate(string url = "searchEngineUrl")
    {
        base.Navigate(url);
    }
}

I want to emphasize that the constructor of this class is marked as private, which prevents the creation of a default constructor.

Tests Using Singleton Design Pattern

Now you can use the page objects directly in your code without the keyword new, thanks to the singleton design pattern.


public class AdvancedSearchEngineSingletonTests
{
    
    public void SetupTest()
    {
        Driver.StartBrowser();
    }

    
    public void TeardownTest()
    {
        Driver.StopBrowser();
    }

    
    public void SearchTextInSearchEngine_Advanced_PageObjectPattern_NoSingleton()
    {
        // Usage without Singleton
        P.SearchEngineMainPage searchEngineMainPage = new P.SearchEngineMainPage();
        searchEngineMainPage.Navigate();
        searchEngineMainPage.Search("Automate The Planet");
        searchEngineMainPage.Validate().ResultsCount(",000 RESULTS");
    }

    
    public void SearchTextInSearchEngine_Advanced_PageObjectPattern_Singleton()
    {
        S.SearchEngineMainPage.Instance.Navigate();
        S.SearchEngineMainPage.Instance.Search("Automate The Planet");
        S.SearchEngineMainPage.Instance.Validate().ResultsCount(",000 RESULTS");
    }
}

Related Articles

Design Patterns

Advanced Specification Design Pattern in Automated Testing

In my last publication from the Design Patterns in Automated Testing Series, I explained to you how you can benefit from the usage of the Specification Design P

Advanced Specification Design Pattern in Automated Testing

Design Patterns

Page Objects That Make Code More Maintainable

If you have read some of my previous posts, most probably you have checked some of my articles about Design Patterns in Automated Testing. One of the most promi

Page Objects That Make Code More Maintainable

Design Patterns

Observer Design Pattern Classic Implementation in Automated Testing

The Observer Design Pattern defines a one-to-many dependency between objects so that when one object changes state, all of its dependents are notified and updat

Observer Design Pattern Classic Implementation in Automated Testing

Design Patterns

Decorator Design Pattern in Automated Testing

In my articles "Strategy Design Pattern" and "Advanced Strategy Design Pattern", I explained the benefits of the application of Strategy Design Pattern in your

Decorator Design Pattern in Automated Testing

Design Patterns

Strategy Design Pattern in Automated Testing

In my previous articles from the series "Design Patterns in Automated Testing", I explained in details how to make your test automation framework better through

Strategy Design Pattern in Automated Testing

Design Patterns

Page Objects- Partial Classes String Properties- WebDriver C#

Editorial Note: I originally wrote this post for the Test Huddle Blog. You can check out the original here, at their site.

Page Objects- Partial Classes String Properties- WebDriver C#
Anton Angelov

About the author

Anton Angelov is Managing Director, Co-Founder, and Chief Test Automation Architect at Automate The Planet — a boutique consulting firm specializing in AI-augmented test automation strategy, implementation, and enablement. He is the creator of BELLATRIX, a cross-platform framework for web, mobile, desktop, and API testing, and the author of 8 bestselling books on test automation. A speaker at 60+ international conferences and researcher in AI-driven testing and LLM-based automation, he has been recognized as QA of the Decade and Webit Changemaker 2025.