Spring Framework — Benefits in Automation

Luis Marchani
Globant
Published in
6 min readFeb 7, 2022

Introduction

The Spring Framework is an application framework and inversion of control container for the Java platform (https://spring.io/). The main idea about using Spring in a Test Automation Project is to take advantage of a few Spring Strategies and apply them by solving different scenarios with it.

The following article is intended for Test Automation Engineers with prior Java and Cucumber experience. The topics to be covered are based on the assumption that the Spring dependency has already been added and configured. The following dependency configuration may be useful for starters:

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>2.1.8.RELEASE</version>

<relativePath/>

</parent>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-test</artifactId>

</dependency>

<dependency>

<groupId>io.cucumber</groupId>

<artifactId>cucumber-spring</artifactId>

<version>${cucumber.version}</version>

</dependency>

We are going to review some scenarios that we usually have to address in test automation:

  • Auto configuration based on the environment.
  • Dependency injection across the project.
  • Different implementations for specific functionality.

Solution

Auto Configuration based on the environment.

When testing, we have to support running our tests against different testing environments. There are many ways to handle this but for this explanation we are going to mix a couple of benefits from Spring: Dependency Injection and Configuration.

Spring provides custom annotations to create, relate, and inject different kinds of objects.

@Configuration

@PropertySource({

“classpath:config/${environment}.db.properties”

})

public class DBManager {

@Bean(destroyMethod = “close”)

public BasicDataSource getDB(@Value(“${url}”) String url,

@Value(“${username}”) String username,

@Value(“${password}”) String password) {

return getDataSourceBuilder(url, username, password);

}

}

In the example above we apply @Configuration and @PropertySource annotations, both are going to support creating a particular object with required information, in this case, a DB controller.

@Configuration will allow us to declare Beans (In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC container are called beans) inside of the class. These Beans are instantiated and placed at Sprint container level, allowing us to later inject and use in anywhere of our code.

The @PropertySource annotation lets us define where the properties are. In this case, we are using an environment variable in the partial path of this field. Meaning, we can declare multiple property files and switch between them to configure a DB controller to access databases in the different testing environments.

Also, we see that the values gathered from the Property files are injected right into the getDB method which makes it easier to understand and implement the functionality. In this case we should’ve created a file with the properties url, username, and password for this to work. Since this method is a Bean, it can be injected anywhere and using the attribute destroyMethod which is particular of the BasicDataSource class, we make sure to kill the connection after using it.

Based on the example above we may have a couple of property source files such as:

qa.db.properties:

url = jdbc:postgresql://qa.db.com:5432/dbName

username = dbusr

password = passwordqa

stg.db.properties:

url = jdbc:postgresql://stg.db.com:5432/dbName

username = dbusr

password = passwordstg

At the time of setting the environment variable “environment” with any of the available environments: qa or stg, the values for url, username and password will get injected into the getDB Bean and the database configuration will be performed with the corresponding values.

We have an instance that we can use anywhere across the project to access a DB and switch between environments. This could be useful for dealing with different URLs, API or DB preconditions, etc.

Dependency Injection across the Project

Now let’s see how Dependency Injection (Related Information) can help us. Many times we need to use the same instance of a Page Object or maybe share something between tasks in Screenplay or just use a library of helpers somewhere.

To explain this, we are going to create a Page Object and see how it works.

@Component

public class ApplicationDash extends BaseSection {

private By wrapperInfo = By.className(“info”);

@Step

public void navigateToInfo(InfoNavigationOption infoNavigationOption) {

//TBD

}

}

Here we have another annotation @Component which auto detects and configures the bean using classpath scanning. So using it this way is going to let us inject this Page Object anywhere.

For example in a Test Step Definition:

public class ApplicationInfoStepDef extends BaseStepDefinition {

@Autowired

ApplicationDash applicationDash;

@Given(“User navigates to Info {}”)

public void userNavigatesToInfo(InfoNavigationOption infoNavigationOption) {

applicationDash.navigateToInfo(infoNavigationOption);

}

}

As you can see we are injecting the applicationDash bean in a step definition, but the step definition doesn’t have the @Component annotation. The reason to do that is the context configuration being used in the runner as follows:

@RunWith(Cucumber.class)

@ActiveProfiles(value = {Profiles.First})

@CucumberContextConfiguration

@CucumberOptions(

//TBD

)

public class TestRunner {

}

}

The active profiles may be overridden through the environment variable: spring.profiles.active which will allow us to select any profile at the time of execution. There’s no need to recompile the solution for the overridden profiles to apply.

The @CucumberContextConfiguration annotation will take care of initializing the step definitions as beans, depending on the attribute glue that should be defined in the @CucumberOptions section:

@CucumberOptions(

glue = {com.domain.steps.location}

)

Also something really important to consider is that Spring needs a way to find the components to be initialized. These components could be anywhere, so we need to tell Spring where to find them. To do that we can create a configuration class with said information:

@Configuration

@ComponentScan(“com.components.location”)

public class Config {

}

And add this configuration to our runner:

@RunWith(Cucumber.class)

@ActiveProfiles(value = {Profiles.First})

@CucumberContextConfiguration

@CucumberOptions(

//TBD

)

@SpringBootTest(classes = Config.class)

public class TestRunner {

}

}

Just by using @Autowired we can inject an instance of the ApplicationDash Page Object. Something similar may happen in a Helper where we mix different page objects to go through a more complex flow.

@Component

public class Helper {

@Autowired

ApplicationDash applicationDash;

@Step

public void doSomething(InfoNavigationOption infoNavigationOption) {

applicationDash.navigateToInfo(infoNavigationOption)

//TBD Some other steps

}

}

We may want to inject some other dependency in the class helper and combine the functionality of many page objects or libraries in a method. Then we can inject the Helper wherever we need it. It’s easy to manage dependencies just by defining and injecting them without taking care of creating new instances and passing parameters to configure objects every time we use them.

Different Implementations for Specific Functionality.

Let’s say our application behaves in different ways depending on user role, switch of application functionality, etc.

For example, we may need to go through a flow that changes a little bit in the middle, something like selecting a date.

For our example we have two ways to select a date in the middle of the flow, the first way would be using a calendar object and the second way would be entering the date as a string.

But the functionality is the same. I need to enter a date.

Ok, so now what happens if this applies to all of the scenarios and we need to test both ways?

This is where Active Profiles takes place.

Let’s create an Interface for such functionality:

public interface IDataManager {

void selectDate(String date);

}

Now we need to implement the different ways to select a date:

@Component

@Profile({Profiles.First})

public class DateFirst implements IDataManager{

@Autowired

CalendarObject calendarObject;

@Override

public void selectDate(String date) {

calendarObject.selectDate(date);

}

}

@Component

@Profile({Profiles.Second})

public class DateSecond implements IDataManager{

@Autowired

CalendarObject calendarObject;

@Override

public void selectDate(String date) {

calendarObject.enter(date);

}

}

Done! We have a couple of implementations for the selectDate functionality. Now we just need to tell Spring to start with the decided implementation.

Let’s explain how using Cucumber for this case:

@RunWith(Cucumber.class)

@ActiveProfiles(value = {Profiles.First})

@CucumberContextConfiguration

@CucumberOptions(

//TBD

)

public class TestRunner {

}

}

The @ActiveProfiles annotation allows us to select one or many profiles to start with. In the example, above we use the “First” Profile which means that the Implementation is marked with @Profile({Profiles.First}) will be the one used when injecting the Interface as follows:

public class ApplicationInfoStepDef extends BaseStepDefinition {

@Autowired

IDataManager iDataManager;

@Given(“Some Step”)

public void someStep(String date) {

iDataManager.selectDate(date);

}

}

Conclusion

Spring is just one tool that may help us perform tasks such as dependency injection, environment management or multiple implementations, making things easier. There are some other frameworks depending on the technology you are using such as Django or Flask for Python, or work with built in functionality in NodeJS, etc. Spring has some features that may seem easier to accomplish using annotations, but the same strategies can be accomplished in different ways. Consider how necessary it is to use Spring in your project and take advantage of all the benefits of it. Spring is a framework with multiple features such as Job Scheduling, Dependency Injection, Authentication, Transactions and it is most used as an MVC Framework. If your scenarios won’t take advantage of most of its features, there are different ways to accomplish the same approach with less resources and maybe more coding, but it’s up to you to make the best decision depending on your project.

Happy Testing!!

Useful information for starters:

https://www.baeldung.com/cucumber-spring-integration

https://thepracticaldeveloper.com/cucumber-tests-spring-boot-dependency-injection/

https://medium.com/@bcarunmail/set-up-and-run-cucumber-tests-in-spring-boot-application-d0c149d26220

--

--