Easily manage Properties Files in Java with Owner
Tags: java, testing
Published: 2021-01-10 21:53:23

This post belongs to the How to create [Lean Test Automation Architecture for Web using Java](http://www.eliasnogueira.com/how-to-create-lean-test-automation-architecture-for-web-using-java-libraries/) series. If you didn’t see the previous posts, please check them out!

## Introduction

A good practice when we are creating an automated test framework using Java is the creation of properties files to add any configuration that can be changed either manually in the file or when you are running the tests using a CI/CD tool.

It’s commonly used to change the URL, main users, timeout, or whatever data you need to change frequently as a configuration.

The main problem is to manage one or more properties files we might have in our project. This article will show you how to easily manage it with Owner Java library.

## The most used way to manage properties files

### The property file

Let’s imagine we will use a property file to manage the different data and/or configuration for a web test. We have this file as an example:

```properties
# target execution: local or grid
target = local

# initial URL
url.base = http://eliasnogueira.com/external/selenium-java-architecture/

# global test timeout
timeout = 3

# headless mode only for chrome or firefox and local execution
headless = false
```

### The old and plain way to read properties file

There are a lot of ways we can read a property file using Java. This example is a simple one using the Singleton design pattern:

```java
public class ReadProperties {

    private static final Logger LOG = LoggerFactory.getLogger(ReadProperties.class);

    private static ReadProperties instance = null;
    private Properties properties = null;

    private ReadProperties() {
        properties = new Properties();
        try {
            InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("general.properties");
            properties.load(inputStream);
        } catch (IOException e) {
            LOG.error(e.getMessage());
        }
    }

    public static synchronized ReadProperties getInstance() {
        if (instance == null) {
            instance = new ReadProperties();
        }
        return instance;
    }

    public String getValue(String key) {
        return this.properties.getProperty(key, String.format("The key %s does not exists!", key));
    }
}
```

Now we can get a value from the property file as:

```java
String url = ReadProperties.getInstance().getValue("url.base");
```

### Possible problems

* Remove the singleton pattern to be able to get the property value from different files
* Manage different files explicitly in your code
* An extra layer (code) to manage the properties into a model to enable readability

## The Owner way
Owner is a library that makes easy the properties file management.

You can easily read properties values by:

* creating a class associating each property name
* using it first creating the property object and after reading its value

### Import the library

You can use Owner importing its library. Please double-check the latest version.

```xml
<dependencies>
    <dependency>
        <groupId>org.aeonbits.owner</groupId>
        <artifactId>owner</artifactId>
        <version>1.0.8</version>
    </dependency>
</dependencies>
```

### How to define the properties class

We will model a class containing the properties as an interface and extending the Owner `Config` class. We also need to use an annotation to define the configuration file.

```java
import org.aeonbits.owner.Config;

@Config.Sources({"classpath:general.properties"})
public interface GeneralConfig extends Config {

    String target();

    @Config.Key("url.base")
    String url();

    int timeout();

    Boolean headless();
}
```

* On line 3, we are telling Owner to load the `general.properties` file that is placed in the classpath
* On line 4, we are defining the model as an Interface (it’s required by Owner) and extending it to use the `Config` class (this class does most of the Owner’s magic).
* From lines 6 to 14, we are defining the properties with the same name we have in the properties file.

Owner will do the trick automatically if you have the property’s names as the same defined in the properties file. Example: the `target()` attribute in the class has the same name as the target property in the general.properties file.

If you have a composite name, as we have for `url.base` you can use the `@Config.Key` annotation to associate the property in the `general.properties` file into the class.

Another benefit is that you can use the correct object types. The headless attribute is a `Boolean`, so Owner will automatically do the type inference.

### How to read the values from the properties file

Basically, we need to create the model and use it! Easy, right?

```java
public class OwnerUsageExample {

   public void example() {
        GeneralConfig generalConfig = ConfigFactory.create(GeneralConfig.class);
    
        String target = generalConfig.target();
        String url = generalConfig.url();
        int timeout = generalConfig.timeout();
        Boolean headlessMode = generalConfig.headless();
    }
 }
```

* On line 4 you can see we have the `GeneralConfig` class and attribute receiving the `ConfigFactory.create(GeneralConfig.class)`. This line will tell Owner to load the `general.properties` file and associate all the properties and values to the `GeneralConfig` class.
* In lines 6 to 9, we are using the attribute `generalConfig` to read the properties values. Each method will return the corresponding property value.

## Dealing with constraints

### Multiples properties file

Do you have multiple properties files to handle? No problem!

You can create one model for each property or you can merge them all in one. The first option you already know after you got here and I will explain the merge approach.

Let’s say you have, in addition to the `general.properties` file, a new one named `grid.properties`.

```properties
grid.url = localhost
grid.port = 4444
```

Now how we can merge both?

```java
import org.aeonbits.owner.Config;

@Config.LoadPolicy(Config.LoadType.MERGE)
@Config.Sources({
        "classpath:general.properties",
        "classpath:grid.properties"
})
public interface GeneralConfig extends Config {

    String target();

    @Config.Key("url.base")
    String url();

    int timeout();

    Boolean headless();

    @Config.Key("grid.url")
    String gridUrl();

    @Config.Key("grid.port")
    String gridPort();
}
```

* On line 3 we are using the `@Config.LoadPolicy(Config.LoadType.MERGE)` annotation to tell Owner to merge files.
* On lines 5 and 6 we have the two properties files declared
* On lines 19 to 23 we have the two new properties mapped
So now you can have only one model do use more than one properties file.

### Read the System Properties

Probably you will use a CI/CD tool to configure your application, run tests, and other magic activities. This is managed, in Java, by the System properties where we can set through the code or using command line parameters. For properties file, we use `-DpropertyName=propertyValue`.

Now we need to tell Owner to look at the System properties.

```java
@Config.LoadPolicy(Config.LoadType.MERGE)
@Config.Sources({
        "system:properties",
        "classpath:general.properties",
        "classpath:grid.properties"
})
public interface GeneralConfig extends Config {
}
```

* On line 3 we added the `"system.properties"` sources, so Owner can first look at it and set the property value

### Create a singleton instance

We were creating the model object to access the properties values using `GeneralConfig generalConfig = ConfigFactory.create(GeneralConfig.class);`

You would add this line in every class you need to access the properties values, right? But you will end up creating multiple instances of the same object. In this case, it should be a singleton.

Owner provides a similar class as `ConfigFactory` but instead of creating a new object every time we need to access the properties values, the `ConfigCache` class can be used.

```java
public class OwnerSingletonUsageExample {

   public void example() {
        GeneralConfig generalConfig = ConfigCache.getOrCreate(GeneralConfig.class);
        // getting values ignored
    }
 }
```

Using `ConfigCache.getOrCreate` Owner will create a single instance of the model. You can learn more about this approach [here](http://owner.aeonbits.org/docs/singleton/).

## Real examples

Would you like to see real examples? I have examples for Web, API, and Mobile projects.

### Automated Web Test

In my [selenium-java-lean-test-architecture](https://github.com/eliasnogueira/selenium-java-lean-test-achitecture) project you can found:

* The [configuration files](https://github.com/eliasnogueira/selenium-java-lean-test-achitecture/tree/master/src/test/resources)
* The [model](https://github.com/eliasnogueira/selenium-java-lean-test-achitecture/blob/master/src/main/java/com/eliasnogueira/config/Configuration.java) for the properties files
* The [Manager](https://github.com/eliasnogueira/selenium-java-lean-test-achitecture/blob/master/src/main/java/com/eliasnogueira/config/ConfigurationManager.java) for the model of the properties files as a static class
* The general code usage
   * In the [factory class](https://github.com/eliasnogueira/selenium-java-lean-test-achitecture/blob/master/src/main/java/com/eliasnogueira/data/BookingDataFactory.java#L44) to get the faker locale
   * In the [driver factory](https://github.com/eliasnogueira/selenium-java-lean-test-achitecture/blob/master/src/main/java/com/eliasnogueira/driver/DriverFactory.java#L38) class to get the target environment
   * In the [remote driver](https://github.com/eliasnogueira/selenium-java-lean-test-achitecture/blob/master/src/main/java/com/eliasnogueira/driver/remote/RemoteDriverManager.java#L53) to get the grid URL and port
   * In the [local driver](https://github.com/eliasnogueira/selenium-java-lean-test-achitecture/blob/master/src/main/java/com/eliasnogueira/driver/local/LocalDriverManager.java#L51) to get the headless mode
   * In the [abstract page object](https://github.com/eliasnogueira/selenium-java-lean-test-achitecture/blob/master/src/main/java/com/eliasnogueira/page/AbstractPageObject.java#L36) class to get the global timeout

### Automated API Test

In my [restassured-complete-basic-example](https://github.com/eliasnogueira/restassured-complete-basic-example) project you can found:

* The [configuration file](https://github.com/eliasnogueira/restassured-complete-basic-example/blob/master/src/test/resources/api.properties)
* The [model](https://github.com/eliasnogueira/restassured-complete-basic-example/blob/master/src/main/java/com/eliasnogueira/credit/config/Configuration.java) for the properties files
* The [Manager](https://github.com/eliasnogueira/restassured-complete-basic-example/blob/master/src/main/java/com/eliasnogueira/credit/config/ConfigurationManager.java) for the model of the properties files as a static class
* The general code usage
   *  In the [base test class](https://github.com/eliasnogueira/restassured-complete-basic-example/blob/master/src/main/java/com/eliasnogueira/credit/base/BaseAPI.java#L46)
   * In the [custom message format](https://github.com/eliasnogueira/restassured-complete-basic-example/blob/master/src/main/java/com/eliasnogueira/credit/commons/MessageFormat.java#L42) class
   * In the [initial state specs](https://github.com/eliasnogueira/restassured-complete-basic-example/blob/master/src/main/java/com/eliasnogueira/credit/specs/InitialStateSpecs.java#L35)

### Automated Mobile Test

In my [appium-parallel-execution](https://github.com/eliasnogueira/appium-parallel-execution) project you can found:

* The [configuration file](https://github.com/eliasnogueira/restassured-complete-basic-example/blob/master/src/test/resources/api.properties)
* The [model](https://github.com/eliasnogueira/appium-parallel-execution/blob/master/src/main/java/com/eliasnogueira/config/Configuration.java) for the properties files
* The [Manager](https://github.com/eliasnogueira/appium-parallel-execution/blob/master/src/main/java/com/eliasnogueira/config/ConfigurationManager.java) for the model of the properties files as a static class
* In the [iOS](https://github.com/eliasnogueira/appium-parallel-execution/blob/master/src/main/java/com/eliasnogueira/driver/manager/IOSDriverManager.java#L57) and [Android](https://github.com/eliasnogueira/appium-parallel-execution/blob/master/src/main/java/com/eliasnogueira/driver/manager/AndroidDriverManager.java#L57) driver manager