Spring Profiles and Reusable Tests, Part 1

When writing test cases for an update to an open source project I was working on, I came across the issue of how to make test classes reusable.

I already had dozens of test cases written for testing a particular class. Now I had written another implementation of that class, and I wanted to reuse the test cases I already had, to avoid duplication.

I decided to see if I could use Spring profiles to do this.

Spring Profiles

Spring bean definition profiles allows the wiring of different beans in different environments.

One use of Spring profiles in testing is where different test data or application configurations can be injected into test cases depending on the profile. There are various examples of this:

However my requirements were a bit different.

  • more specifically I had various implementations of a class that logged an object graph and produce some output in differing formats.
  • I had many test cases written that would test a particular implementation of the logging class and then compare the generated logging with some expected output.
  • I wanted to run the same set of tests with the same test data, but testing different implementations of the logging class

So rather than changing the test data, what I wanted to change was the class under test and the expected results using Spring DI. Using profiles would allow me to reuse the same tests and test data.

Of course if there are only a few tests to run, this could also be achieved using parameterized tests. Then for each test you could use JUnit 4 to pass parameters for the test data and expected test results.

Here are some simple code examples, showing one way to use Spring profiles in tests.

Step 1: A Simple Test with Spring

Note: Here I’m also using Spring’s JavaConfig instead of XML for the application contexts, but either would work.

Firstly let’s create some classes to test. Here are 2 classes that do some string formatting, both of which have a common interface:

public interface Formatter {

  public String format(String name);
}

public class HelloFormatter implements Formatter {

  @Override
  public String format(String name) {
    return "Hello " + name;
  }
}

public class GoodByeFormatter implements Formatter {

  @Override
  public String format(String name) {
    return "Good Bye " + name;
  }
}

Now let’s write a simple JUnit test case for to test ‘HelloFormatter’, using Spring to inject the test class and test data.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={SimpleTestConfig.class})
public class SimpleTest {

	@Autowired
	private Formatter formatter;

	@Autowired
	private String testData;

	@Test
	public void testDave() {
		String result = formatter.format(testData);
		System.out.println("Result = " + result);

		String expected = "Hello Dave";

		assertEquals(expected, result);
	}

	// getters and setters left out for brevity ...
}

And here’s the JavaConfig used to wire the beans for the test.

@Configuration
public class SimpleTestConfig {

	@Bean
	public Formatter formatter()
	{
		return new HelloFormatter();
	}

	@Bean
	public String testData()
	{
		return "Dave";
	}
}

Now if we want to test the ‘GoodbyeFormatter’ implementation class as well, we could just write another test case and another Spring JavaConfig class, but this would mean code duplication.

So instead let’s try using Spring profiles to inject the appropriate test class into the test cases.

Step 2 : Using Spring Profiles

Firstly let’s refactor the Spring JavaConfig classes.

@Configuration
public class CommonTestConfig {

	@Autowired
	LogTestConfiguration logTestConfiguration;

	@Bean
	public Formatter formatter()
	{
		return logTestConfiguration.formatter();
	}

	@Bean
	public String testData()
	{
		return "Dave";
	}
}

public interface LogTestConfiguration {

	public Formatter formatter();

}

@Configuration
@Profile("hello")
public class HelloConfig implements LogTestConfiguration {

	@Bean
	public Formatter formatter()
	{
		return new HelloFormatter();
	}
}

@Configuration
@Profile("goodbye")
public class GoodByeConfig implements LogTestConfiguration {

	@Bean
	public Formatter formatter()
	{
		return new GoodByeFormatter();
	}
}

Notice we have split the configuration between 3 classes now:

  • there is one class that configures the beans required for all the test cases
  • there is one configuration file specifically for testing the ‘HelloFormatter’ class, and it has the annotation ‘@Profile(“hello”)’.
  • there is one configuration file specifically for testing the ‘GoodByeFormatter’ class, and it has the annotation ‘@Profile(“goodbye”)’.
  • the 2 configuration files that have profile annotations share a common interface so that they can, in turn, be injected into the common configuration file

This setup is base on the blog, SPRING 3.1 M1: INTRODUCING @PROFILE, which contains a more detailed explanation.

So here are the refactored test cases that use the new configuration files, one test class to test each formatter implementation class.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={CommonTestConfig.class, HelloConfig.class})
@ActiveProfiles("hello")
public class SpringProfileHelloTest {

	@Autowired
	private Formatter formatter;

	@Autowired
	private String testData;

	@Test
	public void testDave() {
		String result = formatter.format(testData);
		System.out.println("Result = " + result);

		String expected = "Hello Dave";

		assertEquals(expected, result);
	}

	// getters and setters left out for brevity ...
}

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={CommonTestConfig.class, GoodByeConfig.class})
@ActiveProfiles("goodbye")
public class SpringProfileGoodbyeTest {

	@Autowired
	private Formatter formatter;

	@Autowired
	private String testData;

	@Test
	public void testDave() {
		String result = formatter.format(testData);
		System.out.println("Result = " + result);

		String expected = "Good Bye Dave";

		assertEquals(expected, result);
	}

	// getters and setters left out for brevity ...
}

Note that each test case class has the @ActiveProfiles annotation, so that only the appropriate test class is injected for testing. Also we have added the required configuration files to the @ContextConfiguration annotation.

The Sample Code

You can download the sample code for this example, SpringProfileTestingExample1.zip from GitHub.

The requirements to run the sample code are:

Next

We have achieved half of what we set out to do, by using Spring profiles to determine which implementation of a test class is used for each test case.

In the next part of this post, we will make further improvements so that we have one reusable test case able to test both test class implementations by injecting the expected test results as well.

One thought on “Spring Profiles and Reusable Tests, Part 1”

Leave a Reply

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