JUnit together with JavaScript and SVN are some of the technologies that programmers often start using without even reading a single blog post let alone a book. Maybe this is a good thing since they look simple enough and understandable so we can use them right away without any manuals, but this also means that they are also underused. In this article we will go through some features of JUnit that I consider very useful.
Parameterized tests
can use loop but that it would be harder to track down the origin of a possible test failure.
For example if we have the following value object representing rational numbers:
public class RationalNumber {
private final long numerator;
private final long denominator;
public RationalNumber(long numerator, long denominator) {
this.numerator = numerator;
this.denominator = denominator;
}
public long getNumerator() {
return numerator;
}
public long getDenominator() {
return denominator;
}
@Override
public String toString() {
return String.format("%d/%d", numerator, denominator);
}
}
And we have a service class called App with a method convert that divides the number to a rounded value of 5 decimal :
public class App {
/**
* THE Logic
*
* @param number some rational number
* @return BigDecimal rounded to 5 decimal points
*/
public static BigDecimal convert(RationalNumber number) {
BigDecimal numerator = new BigDecimal(number.getNumerator()).
setScale(5, RoundingMode.HALF_UP);
BigDecimal result = numerator.divide(
new BigDecimal(number.getDenominator()),
RoundingMode.HALF_UP);
return result;
}
}
And for the actual AppTest class we have
@RunWith(Parameterized.class)
public class AppTest {
private RationalNumber input;
private BigDecimal expected;
public AppTest(RationalNumber input, BigDecimal expected) {
this.input = input;
this.expected = expected;
}
@Parameterized.Parameters(name = "{index}: number[{0}]= {1}")
public static Collection<Object> data() {
return Arrays.asList(new Object[][]{
{new RationalNumber(1, 2), new BigDecimal("0.50000")},
{new RationalNumber(1, 1), new BigDecimal("1.00000")},
{new RationalNumber(1, 3), new BigDecimal("0.33333")},
{new RationalNumber(1, 5), new BigDecimal("0.20000")},
{new RationalNumber(10000, 3), new BigDecimal("3333.33333")}
});
}
@Test
public void testApp() {
//given the test data
//when
BigDecimal out = App.convert(input);
//then
Assert.assertThat(out, is(equalTo(expected)));
}
}
The Parametrized runner or @RunWith(Parameterized.class) enables the “parametrization” or in other words the injection of the collection of values annotated with @Parameterized.Parameters into the Test constructor where each of the sublist is an parameter list. This means that each of the RationalNumber objects in the data() method will be injected into the input variable and each of the BigDecimal values would be the expected value, so in our example we have 5 tests.
There is also an optional custom naming of the generated test added in the annotation, so “{index}: number[{0}]= {1}” will be replaced with the appropriate parameters defined in the data() method and the “{index}” placeholder will be the test case index, like in the following image
Running the parametrized tests in IntelliJ Idea |
JUnit rules
The simplest definition of JUnit rules is that they are in a sense an interceptors and very similar to the Spring aspect oriented programming or Java EE interceptors API. Basically you can do useful things before and after the test execution.
OK so let’s start with some of the built in test rules. One of them is ExternalResource where the idea is that we setup an external resource and after the teardown garteet the resource was freed up. A classic example of such test is a creation of file, so for that purpose we have a built in class TemporaryFolder but we can also create our own ones for other resources :
public class TheRuleTest {
@Rule
public TemporaryFolder folder = new TemporaryFolder();
@Test
public void someTest() throws IOException {
//given
final File tempFile = folder.newFile("thefile.txt");
//when
tempFile.setExecutable(true) ;
//then
assertThat(tempFile.canExecute(), is(true));
}
}
We could have done this in a @Before and @After blocks and use java temp files but it is easy to forget something and leave some of the files unclosed in some of the scenarios where a test fails.
For example there is also a Timeout rule for methods where if the the execution is not finished in given time limit the test will fail with a Timeout exception. For example to limit the running for 20 milliseconds :
@Rule
public MethodRule globalTimeout = new Timeout(20);
We can implement our own rules that can do a policy enforcement or various project specific changes. The only thing that needs to be done is for us to implement the TestRule interface.
A simple scenario to explain the behaviour is to add a rule that prints someting before and after test.
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
public class MyTestRule implements TestRule {
public class MyStatement extends Statement {
private final Statement statement;
public MyStatement(Statement statement) {
this.statement = statement;
}
@Override
public void evaluate() throws Throwable {
System.out.println("before statement" );
statement.evaluate();
System.out.println("after statement");
}
}
@Override
public Statement apply(Statement statement,
Description description) {
System.out.println("apply rule");
return new MyStatement(statement);
}
}
So now that we have our rule we can use it in tests, were the tests will just print out different values :
public class SomeTest {
@Rule
public MyTestRule folder = new MyTestRule();
@Test
public void testA() {
System.out.println("A");
}
@Test
public void testB() {
System.out.println("B");
}
}
When we run a test the following output will be created on the console output :
apply rule
before statement
A
after statement
apply rule
before statement
B
after statement
From the built in one there is one called ExpectedException that can very useful when trying out testing errors. Additionally there is an option to chain the rules that can be useful in many scenarios.
To sum up
If you wanna say that Spock or TestNG or some library build on top of JUnit have more features than JUnit, than that is probably true.
But you know what? We don’t always have those on our class path and chances are that JUnit is there and already used all over the place. Than why not use it’s full potential ?
Useful links
- Restrictions and details about parametrized tests as well as indexing of attributes and removing the constructor – https://github.com/junit-team/junit/wiki/Parameterized-tests
- JUnit Rules – https://github.com/junit-team/junit/wiki/Rules
- Devoxx talk on JUnit Rules http://www.devoxx.com/display/DV12/JUnit+Rules
- TestNP parameters http://testng.org/doc/documentation-main.html#parameters
- Mixing it up with Spring Runner http://java.dzone.com/articles/writing-parameterized-tests
Meta: this post is part of the Java Advent Calendar and is licensed under the Creative Commons 3.0 Attribution license. If you like it, please spread the word by sharing, tweeting, FB, G+ and so on!
Author: Mite Mitreski
Mite Mitreski works on custom application development and consultancy with primary focus on Java and JVM-based solutions Currently he is working for Klarna in Stockholm on the klarna checkout and it the past he has been JUG Leader of Java User Group, Macedonia. Mite has a great passion for free and open source software, open data formats, and the open web. At the moment is also involved in the development of JSON-P as part of JSR 374.
More on my blog