Testing

AndHow makes testing with multiple configurations easy

Let's write some tests for the ReportGenerator from the Properties examples. Here is that class:

package org.example;

import org.yarnandtail.andhow.GroupInfo;
import org.yarnandtail.andhow.property.*;

import java.math.BigDecimal;
import java.time.LocalDateTime;

public class ReportGenerator {

	@GroupInfo(name="Record filter", desc="Filters are AND'ed together")
	interface Filter {
		StrProp REGION = StrProp.builder()
			.oneOfIgnoringCase("EAST", "WEST").build();
		StrProp ZIP = StrProp.builder().matches("\\d{5}(\\-\\d{4})?")
			.desc("Zipcode w optional plus 4 (12345 or 12345-1234)").build();
		LocalDateTimeProp START_TIME = LocalDateTimeProp.builder()
			.defaultValue(LocalDateTime.parse("2010-01-01T00:00"))
			.desc("Include records after this date-time").build();
		BigDecProp MIN_SALE = BigDecProp.builder()
			.defaultValue(BigDecimal.TEN).greaterThanOrEqualTo(BigDecimal.ZERO)
			.desc("Min sale amount to include").build();
	}

	interface Format {
		DblProp MARGIN = DblProp.builder().defaultValue(1d)
			.greaterThan(.25d).desc("Margin in inches").build();
		BolProp WITH_HEADERS = BolProp.builder().defaultValue(true).build();
	}
}

This class doesn't really do anything, but lets assume its a lambda function that is launched to run a complicated report.

Using andhow.properties on the test classpath

AndHow automatically finds and loads the andhow.properties file at the root of the classpath. Simply place an andhow.properties file at the root of the test classpath to create a configuration used for testing. This is standard feature of how Maven and many other build tools work and will result in a shared configuration for all tests.

Best Practice: Use Property default values for good business-related defaults. Don't use default values local workstation or test environment configuration values.

Since its easy to provide a test configuration file or a local configuration file, don't be tempted to use Property default values for these purposes. Unless configured to a A Property with a default value already has a default value, so there will be no warning if that value

Using the AndHow JUnit Extensions

The JUnit extensions simplify testing multiple configuratino scenarios in your tests. They can be included in a Maven project like this:

<dependency>
	<groupId>org.yarnandtail</groupId>
	<artifactId>andhow-junit5-extensions</artifactId>
	<version>1.5.0</version>
	<scope>test</scope>
</dependency>

The next examples use the extensions.

Customize Property values for a test

Lets say that you need test a scenario for a specific zip code in the 'west' region:

@Test @KillAndHowBeforeThisTest
public void test90210Zip() {
	AndHow.findConfig()
		.addFixedValue(Filter.ZIP, "90210")
		.addFixedValue(Filter.REGION, "west");

	assertTrue("west".equalsIgnoreCase(Filter.REGION.getValue()));
	assertEquals("90210", Filter.ZIP.getValue());
	// ... and do some actual app testing
}

The @KillAndHowBeforeThisTest is one of the AndHow JUnit extensions. It can be placed on an individual test method to reset AndHow to its unconfigured state before the test runs. When the test is done, the original AndHow state is restored (which may be the un-initialized state).

AndHow.findConfig() grabs the configuration of AndHow before it initializes and loads Property values. addFixedValue() effectively hard-codes a specific Property value via the FixedValueLoader.

Assuming no environment vars., system properties or other configuration sources provide named values that match the app's Properties, Property values for the test above would come from:

  • The 'fixed values' for ZIP and REGION

  • andhow.properties on the test classpath (if there is one) for other properties

  • andhow.properties on the main classpath if there is no file on the test classpath

'Killing' and resetting AndHow isn't allowed in production. AndHow Property values are constants and once initialized at startup, do not change. The @KillAndHow... annotations uses reflection to bend the rules to make testing easier.

Custom .properties file for one or more tests

If a test scenario involves setting lots of Property values or is needed for multiple tests, a separate .properties file can be used. Here is an example of one way to do that for an entire test class:

package org.example;

import org.example.ReportGenerator.Filter;
import org.junit.jupiter.api.*;
import org.yarnandtail.andhow.AndHow;
import org.yarnandtail.andhow.junit5.KillAndHowBeforeEachTest;
import static org.junit.jupiter.api.Assertions.assertEquals;

@KillAndHowBeforeEachTest
class TransactionManager_WestSeparateTest {

	@BeforeEach
	public void setup() {
		AndHow.findConfig()
			.setClasspathPropFilePath("/west_region.properties");
	}

	@Test
	public void happyPath() {
		assertEquals("west", Filter.REGION.getValue().toLowerCase());
		// ... and do some actual app testing
	}

	@Test
	public void zipCode90212() {
		AndHow.findConfig()
			.addFixedValue(Filter.ZIP, "90212");

		assertEquals("90212", Filter.ZIP.getValue());
		// ... and do some actual app testing
	}

	// Other tests will also share the same AndHow instance
}

@KillAndHowBeforeEachTest on the test class is the same as putting @KillAndHowBeforeThisTest on each test. All tests in this class will use the west_region.properties set in the @BeforeEach method. Since AndHow is reset before each test, we can even further customize AndHow's configuraiton before an individual test, as in the zipCode90212 test.

If an entire test class needs to run with the same configuration for all tests and/or you don't want AndHow to re-initialize for each test to save a bit of execution time, this can be done slightly differently:

@KillAndHowBeforeAllTests
class TransactionManager_WestSharedTest {

	@BeforeAll
	public static void setup() {
		AndHow.findConfig()
			.setClasspathPropFilePath("/west_region.properties");
		AndHow.instance();  // Optional, but prevents tests from modifying
	}

	@Test
	public void testEastRegionWithWideMargins() {
		assertTrue("West".equalsIgnoreCase(Filter.REGION.getValue()));
	}

	// Other tests will also share the same AndHow instance
}

The @KillAndHowBeforeAllTests JUnit extension resets AndHow a single time before the test class executes its tests. The @BeforeAll method can be used to initialize AndHow as you want it for all the tests. Now all the tests in this class will share the same configuration.

The AndHow.instance() call in @BeforeAll is not necessary, but is a good idea. Without that call, AndHow leaves the setup method uninitialized. One of the test methods could still modify the configuraton (similar to the previous example's zipCode90212() method), leading to confusion. Once AndHow is initialized, it will throw a clear exception if any attempt is made to modify it's configuration.

Last updated