Today I was digging something for one of my custom projects in JUnit’s package and I came across with very nice feature. I think it’s going to make much more sense after my first article about JUnit’s @Parameterized annotation.
Okay, here is what I came up with. I knew a lot about @Parameterized annotation, but it was sometimes hard to use with tests. Sometimes you need to assume couple preconditions for your test and you need to build things on top of that. Otherwise writing unit/integration tests by building lots of required objects for each test is just a headache. Even you try mocking most of them, sometimes it’s really pain to prepare bunch of objects for tests. Of course you can easily use assumeThat assumptions, but they generally don’t make sense alone. Today I discovered @DataPoint annotation under experimental package along with @Theory. Roughly, these guys are helping you to create combination of variables to send them into your test cases. I don’t want to go very deep into how they do this, I’ll give couple examples to explain how we can get benefits of these guys :)
Let’s start with a simple example. Assume that we need to implement a Rock, Paper and Scissor game. And for our purpose, we just want to test the winnig situations. I’m not going to implement the best code ever, just to show the usage of @DataPoints, assume that our implementation is like below:
public class RockPaperScissorEvaluator {
public static enum Hand {
ROCK,
PAPER,
SCISSOR
}
public Integer evaluate(Hand side1, Hand side2) {
if (side1.equals(Hand.ROCK) && !side2.equals(Hand.ROCK)) {
return (side2.equals(Hand.SCISSOR)) ? 1 : -1;
} else if (side1.equals(Hand.SCISSOR) && !side2.equals(Hand.SCISSOR)) {
return (side2.equals(Hand.PAPER)) ? 1 : -1;
} else if (side1.equals(Hand.PAPER) && !side2.equals(Hand.PAPER)) {
return (side2.equals(Hand.ROCK)) ? 1 : -1;
}
return 0;
}
}
As you see, it’s pretty stupid :) To test winning situations, we need to pair Rock/Scissor, Paper/Rock and Scissor/Paper combinations. Let’s do this by creating following test:
@RunWith(Theories.class)
public class RockPaperScissorEvaluatorTest {
@DataPoint public static RockPaperScissorEvaluator.Hand[] hands1 = { RockPaperScissorEvaluator.Hand.ROCK, RockPaperScissorEvaluator.Hand.SCISSOR };
@DataPoint public static RockPaperScissorEvaluator.Hand[] hands2 = { RockPaperScissorEvaluator.Hand.SCISSOR, RockPaperScissorEvaluator.Hand.PAPER };
@DataPoint public static RockPaperScissorEvaluator.Hand[] hands3 = { RockPaperScissorEvaluator.Hand.PAPER, RockPaperScissorEvaluator.Hand.ROCK };
RockPaperScissorEvaluator evaluator = new RockPaperScissorEvaluator();
@Theory
public void testWinners(RockPaperScissorEvaluator.Hand[] sides) {
assertEquals(new Integer(1), evaluator.evaluate(sides[0], sides[1]));
}
}
Okay what’s happening above! First of all we’re setting our Runner class to Theories.class. As you may already know, it’s gonna mark this test class for running with Theories.class instance. After that, we created couple public static class variables. Theories runner will use these variables (by all known combinations) to pass to your test methods marked by @Theory annotation. In this case we have only one test method and it accepts a single parameter. If you would have more than one parameter, Theories.class was going to try all possible combinations of the @DataPoints.
Another example would be ‘transfering money’, ‘withdrawing money’ from an account object. I know these are already stupid examples, but they make things easier to understand. Okay, assume that we have following Account class:
public class Account {
private BigDecimal balance = BigDecimal.ZERO;
public Account(BigDecimal balance) {
this.balance = balance;
}
public Boolean withdraw(BigDecimal amount) {
if (!hasEnoughBalance(amount)) {
return false;
}
balance = balance.subtract(amount);
return true;
}
public void deposit(BigDecimal amount) {
balance = balance.add(amount);
}
public Boolean transfer(BigDecimal amount, Account toAccount) {
if (!hasEnoughBalance(amount)) {
return false;
}
balance = balance.subtract(amount);
toAccount.deposit(amount);
return true;
}
private Boolean hasEnoughBalance(BigDecimal amount) {
return balance.compareTo(amount) >= 0;
}
public BigDecimal getBalance() {
return this.balance;
}
}
It’s just another simple Account class that you can find anywhere in the net :) Anyways, in this case assume that we want to test withdrawal of certain amount from different accounts. Here is how our test would look like:
@RunWith(Theories.class)
public class AccountTest {
@DataPoint public static Account account1 = new Account(new BigDecimal("30"));
@DataPoint public static Account account2 = new Account(new BigDecimal("3"));
@DataPoint public static Account account3 = new Account(new BigDecimal("75"));
@DataPoint public static Account account4 = new Account(new BigDecimal("90"));
@DataPoint public static Account account5 = new Account(new BigDecimal("100"));
@Theory
public void testWithdraw(Account account) {
assumeThat(account.getBalance(), Matchers.greaterThan(BigDecimal.TEN));
assertTrue(account.withdraw(BigDecimal.TEN));
}
}
As you may quickly notice, we introduced 5 different accounts with different balances. And in our test case, we’re trying to withdraw $10 from each of them. One thing here is assumeThat assumption. You can get more information at JUnit Documentation. Basically it just tells to the runner that it should stop execution of the test if it doesn’t satisfy the assumption. In our example, it means that it’s going to skip the test for account2.
Now let’s test transferring some money from one random account to another. Here how we would do:
@RunWith(Theories.class)
public class AccountTest {
@DataPoint
public static Account account1 = new Account(new BigDecimal("30"));
@DataPoint
public static Account account2 = new Account(new BigDecimal("3"));
@DataPoint
public static Account account3 = new Account(new BigDecimal("75"));
@DataPoint
public static Account account4 = new Account(new BigDecimal("90"));
@DataPoint
public static Account account5 = new Account(new BigDecimal("100"));
@Theory
public void testTransferringMoney(Account johnDoe, Account janeDoe) {
assumeThat(johnDoe.getBalance(), Matchers.greaterThan(new BigDecimal("25")));
assertTrue(johnDoe.transfer(new BigDecimal("25"), janeDoe));
}
}
In this test, our runner is going to create all possible combinations of 2 accounts and pass them to our testTransferringMoney test method. And also it’s going to skip the combinations for any account which has less than $25. One thing here is I used Hamcrest library for numerical comparisons. It’s a great library to improve your testing. Just have a look when you have time, you won’t regret :)
Okay, that’s it for now. Hope it helps! You can download the sample project from this link.









