How To Use ThreadSafe Singleton Class To Manage Instance Variables In Automation Framework – Java

Introduction

In the previous post, we have seen how maximum beginners and experienced Selenium professionals apply inheritance with TestNG annotations while setup test scripts and end up with encountering NullPointerException. We have solved NullPointerException by converting instance variables to Static variables.

We also solved the above problem using a Singleton design pattern as well. I have used a static reference of class SharedVariables which will create a problem while running in parallel for sure. So in this post, we will use ThreadLocal static instance of class SharedVariables.

Dependency version details

I used below version of TestNG in this post:-



    org.testng
    testng
    7.1.0
    test

Problem with static variables

Before we jump to ThreadLocal implementation, Let’s create a scenario in which we will face incorrect mapping of data while running in parallel.

There is no change in class SharedVariable and SharedVariableMap.

Class SharedVariable

package SecondTestNullPointerExceptionWithSingletonParallelProblem;

public class SharedVariables {

	// Private instance of class
	private static final SharedVariables instance = new SharedVariables();

	// Private constructor to control object creation of class
	private SharedVariables() {
	}

	// Return unidealized instance of class 
	public static SharedVariables getInstance() {
		return instance;
	}

	// A private variable with getter and setter methods
	private String someVariable;

	public String getSomeVariable() {
		return someVariable;
	}

	public void setSomeVariable(String someVariable) {
		this.someVariable = someVariable;
	}
}

Class SharedVariableMap

package SecondTestNullPointerExceptionWithSingletonParallelProblem;

import java.util.HashMap;

public class SharedVariablesMap {

	// Private instance of class
	private static final SharedVariablesMap instance = new SharedVariablesMap();
	
	private static HashMap dataStore = new HashMap<>();

	// Private constructor to control object creation of class
	private SharedVariablesMap() {
	}

	// Return unidealized instance of class 
	public static SharedVariablesMap getInstance() {
		return instance;
	}
	
	public void store(String key, String value)
	{
		dataStore.put(key, value);
	}
	
	public Object get(String key)
	{
		return dataStore.get(key);
	}
}

Add thread details for understanding calling flow well and random wait in Setup class and only thread details in test methods classes. I will also change annotation BeforeSuite to BeforeMethod in Setup class. I will run methods in parallel so there will be a call to setupVariable() for each method by a unique thread and may override other’s values.

Class Setup

package SecondTestNullPointerExceptionWithSingletonParallelProblem;

import java.util.Random;

import org.testng.annotations.BeforeMethod;

public class Setup {

	// Get same instance of SharedVariables always
	SharedVariables sharedVariables = SharedVariables.getInstance();
	SharedVariablesMap sharedVariablesMap = SharedVariablesMap.getInstance();

	@BeforeMethod
	public void setupVariable() throws InterruptedException {
		System.out.println("Executing setupVariable By Thread "+Thread.currentThread().getId());
		String somevalue = "someValue" + new Random().nextInt();
		String name = "Amod" + new Random().nextInt();
		System.out.println("Value set for SomeVariable by Thread "+Thread.currentThread().getId() + " as " + somevalue);
		System.out.println("Value set for Name by Thread "+Thread.currentThread().getId() + " as "+name);
		// Making thread sleep
		int waitTime = new Random().nextInt((9 - 1) + 1) + 1;
		System.out.println("Waiting for : "+waitTime+" sec");
		Thread.sleep(waitTime*1000);
		sharedVariables.setSomeVariable(somevalue);
		sharedVariablesMap.store("Name", name);
	}

}

Class FirstTestNGClass

package SecondTestNullPointerExceptionWithSingletonParallelProblem;

import org.testng.annotations.Test;

public class FirstTestNGClass extends Setup{
	
	@Test
	public void firstMethodOfFirstTestNGClass()
	{
		System.out.println("Executing firstMethodOfFirstTestNGClass by Thread "+ Thread.currentThread().getId());
		System.out.println("Value of someVariable is : "+ sharedVariables.getSomeVariable());
		System.out.println("Value of name is :"+ sharedVariablesMap.get("Name"));
	}
	
	@Test
	public void secondMethodOfFirstTestNGClass()
	{
		System.out.println("Executing secondMethodOfFirstTestNGClass by Thread " + Thread.currentThread().getId());
		System.out.println("Value of someVariable is : "+ sharedVariables.getSomeVariable());
		System.out.println("Value of name is :"+ sharedVariablesMap.get("Name"));
	}
}

Class SecondTestNGClass

package SecondTestNullPointerExceptionWithSingletonParallelProblem;

import org.testng.annotations.Test;

public class SecondTestNGClass extends Setup{
	
	@Test
	public void firstMethodOfSecondTestNGClass()
	{
		System.out.println("Executing firstMethodOfSecondTestNGClass by Thread "+Thread.currentThread().getId());
		System.out.println("Value of someVariable is : "+ sharedVariables.getSomeVariable());
		System.out.println("Value of name is :"+ sharedVariablesMap.get("Name"));
	}
	
	@Test
	public void secondMethodOfSecondTestNGClass()
	{
		System.out.println("Executing secondMethodOfSecondTestNGClass by Thread "+ Thread.currentThread().getId());
		System.out.println("Value of someVariable is : "+ sharedVariables.getSomeVariable());
		System.out.println("Value of name is :"+ sharedVariablesMap.get("Name"));
	}
}

Create a TestNG xml just to run methods in parallel.




  
    
      
      
    
   
  
  
    
    
      
    
  
 

Output

[RemoteTestNG] detected TestNG version 7.0.1
Executing setupVariable By Thread 12
Executing setupVariable By Thread 14
Value set for SomeVariable by Thread 12 as someValue566752212
Value set for Name by Thread 12 as Amod759456508
Executing setupVariable By Thread 11
Executing setupVariable By Thread 13
Value set for SomeVariable by Thread 11 as someValue181282116
Value set for Name by Thread 11 as Amod89764154
Waiting for : 7 sec
Value set for SomeVariable by Thread 14 as someValue547002893
Value set for Name by Thread 14 as Amod-778378531
Waiting for : 8 sec
Value set for SomeVariable by Thread 13 as someValue-146738648
Waiting for : 2 sec
Value set for Name by Thread 13 as Amod322004321
Waiting for : 7 sec
Executing secondMethodOfSecondTestNGClass by Thread 14
Value of someVariable is : someValue547002893
Value of name is :Amod-778378531
Executing secondMethodOfFirstTestNGClass by Thread 12
Value of someVariable is : someValue566752212
Value of name is :Amod759456508
Executing firstMethodOfSecondTestNGClass by Thread 13
Value of someVariable is : someValue566752212
Value of name is :Amod759456508
Executing firstMethodOfFirstTestNGClass by Thread 11
Value of someVariable is : someValue181282116
Value of name is :Amod89764154
Executing setupVariable By Thread 16
Value set for SomeVariable by Thread 16 as someValue-253812863
Value set for Name by Thread 16 as Amod-2053621935
Waiting for : 4 sec
Executing setupVariable By Thread 15
Value set for SomeVariable by Thread 15 as someValue158143974
Value set for Name by Thread 15 as Amod1765012539
Waiting for : 3 sec
Executing setupVariable By Thread 17
Value set for SomeVariable by Thread 17 as someValue-1485281520
Executing setupVariable By Thread 18
Value set for SomeVariable by Thread 18 as someValue340008154
Value set for Name by Thread 18 as Amod846378800
Waiting for : 2 sec
Value set for Name by Thread 17 as Amod1362387569
Waiting for : 1 sec
Executing firstMethodOfSecondTestNGClass by Thread 17
Value of someVariable is : someValue-1485281520
Value of name is :Amod1362387569
Executing secondMethodOfSecondTestNGClass by Thread 18
Value of someVariable is : someValue340008154
Value of name is :Amod846378800
Executing firstMethodOfFirstTestNGClass by Thread 15
Value of someVariable is : someValue158143974
Value of name is :Amod1765012539
Executing secondMethodOfFirstTestNGClass by Thread 16
Value of someVariable is : someValue-253812863
Value of name is :Amod-2053621935

===============================================
Suite
Total tests run: 8, Passes: 8, Failures: 0, Skips: 0
===============================================

Lengthy output to understand and I may not be able to explain well. Let’s group console output by thread ids as below.

Thread 11

Executing setupVariable By Thread 11
alue set for SomeVariable by Thread 11 as someValue181282116
Value set for Name by Thread 11 as Amod89764154
Executing firstMethodOfFirstTestNGClass by Thread 11
Value of someVariable is : someValue181282116
Value of name is :Amod89764154

Thread 12

Executing setupVariable By Thread 12
Value set for SomeVariable by Thread 12 as someValue566752212
Value set for Name by Thread 12 as Amod759456508
Executing secondMethodOfFirstTestNGClass by Thread 12
Value of someVariable is : someValue566752212
Value of name is :Amod759456508

Thread 13

Executing setupVariable By Thread 13
Value set for SomeVariable by Thread 13 as someValue-146738648
Value set for Name by Thread 13 as Amod322004321
Executing firstMethodOfSecondTestNGClass by Thread 13
Value of someVariable is : someValue566752212
Value of name is :Amod759456508

Thread 14

Executing setupVariable By Thread 14
Value set for SomeVariable by Thread 14 as someValue547002893
Value set for Name by Thread 14 as Amod-778378531
Executing secondMethodOfSecondTestNGClass by Thread 14
Value of someVariable is : someValue547002893
Value of name is :Amod-778378531

I have just listed for 4 thread ids. Values initialized by Thread 11 and Thread 14 are consumed by test method called by Thread 11 and Thread 14 respectively. You can see values are matched. This behavior will change in each run.

Now observe carefully for Thread 12 and Thread 13. Thread 12 has no issues but test methods called by Thread 13 have not used values initialized by Thread 13 but by Thread 12. As we are running methods in parallel so each Test method should have their own values but here it is messed. This is a problem while running parallel with static variables.

Solving Problem with static variables during parallel run using ThreadLocal

I have explained ThreadLocal in my previous posts. You can check those in the prerequisite section. In this section, we will just focus on the implementation of ThreadLocal.

Let’s have instance variables as ThreadLocal in both SharedVariable and SharedvariableMap classes.

Class SharedVariables

package SecondTestNullPointerExceptionWithSingletonParallelProblemSolution;

public class SharedVariables {

	// Private instance of class
	private static final ThreadLocal instance = new ThreadLocal();

	// Private constructor to control object creation of class
	private SharedVariables() {
	}

	// Return unidealized instance of class 
	public static SharedVariables getInstance() {
		if(instance.get() == null)
			instance.set(new SharedVariables());
		return instance.get();
	}

	// A private variable with getter and setter methods
	private String someVariable;

	public String getSomeVariable() {
		return someVariable;
	}

	public void setSomeVariable(String someVariable) {
		this.someVariable = someVariable;
	}
}

Class SharedVariablesMap

package SecondTestNullPointerExceptionWithSingletonParallelProblemSolution;

import java.util.HashMap;

public class SharedVariablesMap {

	// Private instance of class
	private static final ThreadLocal instance = new ThreadLocal();
	
	private static final ThreadLocal> dataStore = new ThreadLocal>();
	
	// Private constructor to control object creation of class
	private SharedVariablesMap() {
	}

	// Return unidealized instance of class 
	public static SharedVariablesMap getInstance() {
		if(instance.get() == null)
		{
			instance.set(new SharedVariablesMap());
			dataStore.set(new HashMap());
		}
		return instance.get();
			
	}
	
	public void store(String key, String value)
	{
		dataStore.get().put(key, value);
	}
	
	public Object get(String key)
	{
		return dataStore.get().get(key);
	}
}

Class Setup

I have not initialized class variables sharedVariables and sharedVariablesMap as we are running methods in parallel. If we initialized while declaration, these will be initialized commonly for all test methods when class Setup will be loaded into memory and you will see incorrect data mapping among threads again.

package SecondTestNullPointerExceptionWithSingletonParallelProblemSolution;

import java.util.Random;

import org.testng.annotations.BeforeMethod;

public class Setup {

	// Get same instance of SharedVariables always
	SharedVariables sharedVariables;
	SharedVariablesMap sharedVariablesMap;

	@BeforeMethod
	public void setupVariable() throws InterruptedException {
		sharedVariables = SharedVariables.getInstance();
		sharedVariablesMap = SharedVariablesMap.getInstance();
		System.out.println("Executing setupVariable By Thread "+Thread.currentThread().getId());
		String somevalue = "someValue" + new Random().nextInt();
		String name = "Amod" + new Random().nextInt();
		System.out.println("Value set for SomeVariable by Thread "+Thread.currentThread().getId() + " as " + somevalue);
		System.out.println("Value set for Name by Thread "+Thread.currentThread().getId() + " as "+name);
		// Making thread sleep
		int waitTime = new Random().nextInt((9 - 1) + 1) + 1;
		System.out.println("Waiting for : "+waitTime+" sec");
		Thread.sleep(waitTime*1000);
		sharedVariables.setSomeVariable(somevalue);
		sharedVariablesMap.store("Name", name);
	}

}

There is no change in Test classes. Below is TestNG xml.

TestNG XML




  
    
      
      
    
   
  
  
    
    
      
    
  
 

Output

[RemoteTestNG] detected TestNG version 7.0.1
Executing setupVariable By Thread 12
Executing setupVariable By Thread 11
Executing setupVariable By Thread 14
Executing setupVariable By Thread 13
Value set for SomeVariable by Thread 14 as someValue-1231067331
Value set for Name by Thread 14 as Amod-396767176
Value set for SomeVariable by Thread 12 as someValue-904666002
Value set for SomeVariable by Thread 11 as someValue200196728
Value set for Name by Thread 11 as Amod-88766989
Waiting for : 3 sec
Value set for Name by Thread 12 as Amod-1484453836
Value set for SomeVariable by Thread 13 as someValue1523456635
Value set for Name by Thread 13 as Amod107048814
Waiting for : 5 sec
Waiting for : 6 sec
Waiting for : 9 sec
Executing secondMethodOfSecondTestNGClass by Thread 14
Value of someVariable is : someValue-1231067331
Value of name is :Amod-396767176
Executing firstMethodOfSecondTestNGClass by Thread 13
Value of someVariable is : someValue1523456635
Value of name is :Amod107048814
Executing secondMethodOfFirstTestNGClass by Thread 12
Value of someVariable is : someValue-904666002
Value of name is :Amod-1484453836
Executing firstMethodOfFirstTestNGClass by Thread 11
Value of someVariable is : someValue200196728
Value of name is :Amod-88766989
Executing setupVariable By Thread 16
Value set for SomeVariable by Thread 16 as someValue1686235698
Value set for Name by Thread 16 as Amod-1623769295
Waiting for : 2 sec
Executing setupVariable By Thread 15
Value set for SomeVariable by Thread 15 as someValue-394860423
Value set for Name by Thread 15 as Amod879328108
Waiting for : 1 sec
Executing setupVariable By Thread 18
Value set for SomeVariable by Thread 18 as someValue-798221713
Value set for Name by Thread 18 as Amod173745892
Waiting for : 9 sec
Executing setupVariable By Thread 17
Value set for SomeVariable by Thread 17 as someValue-214829912
Value set for Name by Thread 17 as Amod292220200
Waiting for : 6 sec
Executing firstMethodOfFirstTestNGClass by Thread 15
Value of someVariable is : someValue-394860423
Value of name is :Amod879328108
Executing secondMethodOfFirstTestNGClass by Thread 16
Value of someVariable is : someValue1686235698
Value of name is :Amod-1623769295
Executing firstMethodOfSecondTestNGClass by Thread 17
Value of someVariable is : someValue-214829912
Value of name is :Amod292220200
Executing secondMethodOfSecondTestNGClass by Thread 18
Value of someVariable is : someValue-798221713
Value of name is :Amod173745892

===============================================
Suite
Total tests run: 8, Passes: 8, Failures: 0, Skips: 0
===============================================

Let’s group them by thread ids.

Executing setupVariable By Thread 11
Value set for SomeVariable by Thread 11 as someValue200196728
Value set for Name by Thread 11 as Amod-88766989
Executing firstMethodOfFirstTestNGClass by Thread 11
Value of someVariable is : someValue200196728
Value of name is :Amod-88766989

Executing setupVariable By Thread 12
Value set for SomeVariable by Thread 12 as someValue-904666002
Value set for Name by Thread 12 as Amod-1484453836
Executing secondMethodOfFirstTestNGClass by Thread 12
Value of someVariable is : someValue-904666002
Value of name is :Amod-1484453836

Executing setupVariable By Thread 13
Value set for SomeVariable by Thread 13 as someValue1523456635
Value set for Name by Thread 13 as Amod107048814
Executing firstMethodOfSecondTestNGClass by Thread 13
Value of someVariable is : someValue1523456635
Value of name is :Amod107048814

Executing setupVariable By Thread 14
Value set for SomeVariable by Thread 14 as someValue-1231067331
Value set for Name by Thread 14 as Amod-396767176
Executing secondMethodOfSecondTestNGClass by Thread 14
Value of someVariable is : someValue-1231067331
Value of name is :Amod-396767176

Executing setupVariable By Thread 15
Value set for SomeVariable by Thread 15 as someValue-394860423
Value set for Name by Thread 15 as Amod879328108
Executing firstMethodOfFirstTestNGClass by Thread 15
Value of someVariable is : someValue-394860423
Value of name is :Amod879328108

Executing setupVariable By Thread 16
Value set for SomeVariable by Thread 16 as someValue1686235698
Value set for Name by Thread 16 as Amod-1623769295
Executing secondMethodOfFirstTestNGClass by Thread 16
Value of someVariable is : someValue1686235698
Value of name is :Amod-1623769295

Executing setupVariable By Thread 17
Value set for SomeVariable by Thread 17 as someValue-214829912
Value set for Name by Thread 17 as Amod292220200
Executing firstMethodOfSecondTestNGClass by Thread 17
Value of someVariable is : someValue-214829912
Value of name is :Amod292220200

Executing setupVariable By Thread 18
Value set for SomeVariable by Thread 18 as someValue-798221713
Value set for Name by Thread 18 as Amod173745892
Executing secondMethodOfSecondTestNGClass by Thread 18
Value of someVariable is : someValue-798221713
Value of name is :Amod173745892

This time you can see there is no wrong mapping of data and each thread was able to access correct values.

You can download/clone the above sample project from here.

If you have any doubt, feel free to comment below.
If you like my posts, please like, comment, share and subscribe.
#ThanksForReading
#HappyLearning

Find all Selenium related posts here, all API manual and automation related posts here, and find frequently asked Java Programs here.

Many other topics you can navigate through the menu.

1 thought on “How To Use ThreadSafe Singleton Class To Manage Instance Variables In Automation Framework – Java”

  1. Hi Amod Nice article, Can we able to use same singleton pattern in Selenium C# to handle threadsafe webdriver?

Leave a Reply

Your email address will not be published. Required fields are marked *