Recently while testing a spring-based app, we required the ability to change the test case values frequently. We had the tests, mocking framework and test suites but we needed to have the ability to choose what values get passed to the test cases for the next run we trigger, especially for business use cases.
The usual habit was to have the JUnit test classes declare tests and assert, as anyone would do, but how would that change the inputs/expected values for a single run?
Enter - Externalizing the test case configuration!
One of the highlights of spring is DI: Dependency Inject a.k.a. Inversion of Control. That’s a ‘techy’ term for a simple (yet powerful) way to design classes, which means that the class allows its dependencies (Has-a) ‘injected’ in it at the time of instantiation. Well, we won’t be discussing what DI means here (I have made a promise that I will keep this and next few posts short!), there is plenty of documentation available online. But this just sets the context.
Lets assume that we want to test our class “Superb”, and our test class is “SuperbTest”
Let’s begin then. This is how our usual testContext.xml file would look like:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:annotation-config />
<context:component-scan base-package="com.demoapps" />
<context:property-placeholder location="classpath:TestCaseProps.properties" />
</beans>
Nothing special here or in the part that we are reading a TestConfigProps.properties file, but what that file has is somewhat interesting. And what does the properties file look like?
#Superb
Superb.input.users.user1=Nikhil
Superb.output.users.user1=Smart
Superb.input.users.user2=Nikhil2
Superb.output.users.user2=Wow
Superb.currentSetup.users=user1
We have to agree to a simple naming convention for the property names and we have an input/output matching pattern. The pattern dictates that we retain the category (“users”) and the id (“1”/”2”) across the input and output groups and we can match them to test output.
The last part of the puzzle:
package com.demoapps;
// imports
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations= {"classpath:testContext.xml"}) // and others, if you have.
public class SuperbTest {
@Value("${Superb.input.users.${Superb.currentSetup.users}}")
String testInput;
@Value("${Superb.output.users.${Superb.currentSetup.users}}")
String testOutput;
@Test
public void myTest() {
assertEquals(SomeClass.someAwesomeMethod(testInput), testOutput);
}
}
That was simple.
This method lets us externalize simple input and output values for tests. Although this lets us externalize output values, we now realize that we use it more to externalize only the inputs. Especially in cases where the inputs need to change from time to time. Also, it works best with Strings - other types, not so much.
A drawback of this method is that it works only with simple types. You cannot externalize large objects this way. But maybe it can be enhanced to accept paths to serialized objects instead.