Core Java

Spice up your test code with custom assertions

Inspired by the @tkaczanowski talk during GeeCON conference I decided to have a closer look at custom assertions with AssertJ library.

In my ‘Dice’ game I created a ‘Chance’ that is any combination of dice with the score calculated as a sum of all dice. This is relatively simple object:
 
 
 
 
 
 

class Chance implements Scorable {

    @Override
    public Score getScore(Collection<Dice> dice) {
        int sum = dice.stream()
                .mapToInt(die -> die.getValue())
                .sum();
        return scoreBuilder(this)
                .withValue(sum)
                .withCombination(dice)
                .build();
    }
}

public interface Scorable {
    Score getScore(Collection<Dice> dice);
}

In my test I wanted to see how the score is calculated for different dice combination. I started with simple (and only one actually):

public class ChanceTest {

    private Chance chance = new Chance();

    @Test
    @Parameters
    public void chance(Collection<Dice> rolled, int scoreValue) {
        // arrange
        Collection<Dice> rolled = dice(1, 1, 3, 3, 3);
        // act
        Score score = chance.getScore(rolled);
        // assert
        assertThat(actualScore.getScorable()).isNotNull();
        assertThat(actualScore.getValue()).isEqualTo(expectedScoreValue);
        assertThat(actualScore.getReminder()).isEmpty();
        assertThat(actualScore.getCombination()).isEqualTo(rolled);
    }


}

A single concept – score object – is validated in the test. To improve the readability and reusability of the score validation I will create a custom assertion. I would like my assertion is used like any other AssertJ assertion as follows:

public class ChanceTest {

    private Chance chance = new Chance();

    @Test
    public void scoreIsSumOfAllDice() {
        Collection<Dice> rolled = dice(1, 1, 3, 3, 3);
        Score score = chance.getScore(rolled);

        ScoreAssertion.assertThat(score)
                .hasValue(11)
                .hasNoReminder()
                .hasCombination(rolled);
    }
}

In order to achieve that I need to create a ScoreAssertion class that extends from org.assertj.core.api.AbstractAssert. The class should have a public static factory method and all the needed verification methods. In the end, the implementation may look like the below one.

class ScoreAssertion extends AbstractAssert<ScoreAssertion, Score> {

    protected ScoreAssertion(Score actual) {
        super(actual, ScoreAssertion.class);
    }

    public static ScoreAssertion assertThat(Score actual) {
        return new ScoreAssertion(actual);
    }

    public ScoreAssertion hasEmptyReminder() {
        isNotNull();
        if (!actual.getReminder().isEmpty()) {
            failWithMessage("Reminder is not empty");
        }
        return this;
    }

    public ScoreAssertion hasValue(int scoreValue) {
        isNotNull();
        if (actual.getValue() != scoreValue) {
            failWithMessage("Expected score to be <%s>, but was <%s>", 
                    scoreValue, actual.getValue());
        }
        return this;
    }

    public ScoreAssertion hasCombination(Collection<Dice> expected) {
        Assertions.assertThat(actual.getCombination())
                .containsExactly(expected.toArray(new Dice[0]));
        return this;
    }
}

The motivation of creating such an assertion is to have more readable and reusable code. But it comes with some price – more code needs to be created. In my example, I know I will create more Scorables quite soon and I will need to verify their scoring algorithm, so creating an additional code is justified. The gain will be visible. For example, I created a NumberInARow class that calculates the score for all consecutive numbers in a given dice combination. The score is a sum of all dice with the given value:

class NumberInARow implements Scorable {

    private final int number;

    public NumberInARow(int number) {
        this.number = number;
    }

    @Override
    public Score getScore(Collection<Dice> dice) {

        Collection<Dice> combination = dice.stream()
                .filter(value -> value.getValue() == number)
                .collect(Collectors.toList());

        int scoreValue = combination
                .stream()
                .mapToInt(value -> value.getValue())
                .sum();

        Collection<Dice> reminder = dice.stream()
                .filter(value -> value.getValue() != number)
                .collect(Collectors.toList());

        return Score.scoreBuilder(this)
                .withValue(scoreValue)
                .withReminder(reminder)
                .withCombination(combination)
                .build();
    }
}

I started with the test that checks a two fives in a row and I already missed on assertion – hasReminder – so I improved the ScoreAssertion. I continued with changing the assertion with other tests until I got quite well shaped DSL I can use in my tests:

public class NumberInARowTest {

    @Test
    public void twoFivesInARow() {
        NumberInARow numberInARow = new NumberInARow(5);
        Collection<Dice> dice = dice(1, 2, 3, 4, 5, 5);
        Score score = numberInARow.getScore(dice);
        
        // static import ScoreAssertion
        assertThat(score)
                .hasValue(10)
                .hasCombination(dice(5, 5))
                .hasReminder(dice(1, 2, 3, 4));
    }

    @Test
    public void noNumbersInARow() {
        NumberInARow numberInARow = new NumberInARow(5);
        Collection<Dice> dice = dice(1, 2, 3);
        Score score = numberInARow.getScore(dice);

        assertThat(score)
                .isZero()
                .hasReminder(dice(1, 2, 3));
    }
}

public class TwoPairsTest {

    @Test
    public void twoDistinctPairs() {
        TwoPairs twoPairs = new TwoPairs();
        Collection<Dice> dice = dice(2, 2, 3, 3, 1, 4);
        Score score = twoPairs.getScore(dice);

        assertThat(score)
                .hasValue(10)
                .hasCombination(dice(2, 2, 3, 3))
                .hasReminder(dice(1, 4));
    }
}

The assertion after changes looks as follows:

class ScoreAssertion extends AbstractAssert<ScoreAssertion, Score> {

    protected ScoreAssertion(Score actual) {
        super(actual, ScoreAssertion.class);
    }

    public static ScoreAssertion assertThat(Score actual) {
        return new ScoreAssertion(actual);
    }

    public ScoreAssertion isZero() {
        hasValue(Score.ZERO);
        hasNoCombination();
        return this;
    }

    public ScoreAssertion hasValue(int scoreValue) {
        isNotNull();
        if (actual.getValue() != scoreValue) {
            failWithMessage("Expected score to be <%s>, but was <%s>",
                    scoreValue, actual.getValue());
        }
        return this;
    }

    public ScoreAssertion hasNoReminder() {
        isNotNull();
        if (!actual.getReminder().isEmpty()) {
            failWithMessage("Reminder is not empty");
        }
        return this;
    }

    public ScoreAssertion hasReminder(Collection<Dice> expected) {
        isNotNull();
        Assertions.assertThat(actual.getReminder())
                .containsExactly(expected.toArray(new Dice[0]));
        return this;
    }

    private ScoreAssertion hasNoCombination() {
        isNotNull();
        if (!actual.getCombination().isEmpty()) {
            failWithMessage("Combination is not empty");
        }
        return this;
    }

    public ScoreAssertion hasCombination(Collection<Dice> expected) {
        isNotNull();
        Assertions.assertThat(actual.getCombination())
                .containsExactly(expected.toArray(new Dice[0]));
        return this;
    }
}

I really like the idea of custom AssertJ assertions. They will improve the readability of my code in certain cases. On the other hand, I am pretty sure they cannot be used in all scenarios. Especially in those, where the chance of reusability is minimal. In such a case private methods with grouped assertions can be used.

What is your opinion?

Resources

Reference: Spice up your test code with custom assertions from our JCG partner Rafal Borowiec at the Codeleak.pl blog.

Rafal Borowiec

Software developer, Team Leader, Agile practitioner, occasional blogger, lecturer. Open Source enthusiast, quality oriented and open-minded.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button