Home » Java » Core Java » Spice up your test code with custom assertions

About Rafal Borowiec

Rafal is an IT specialist with about 8 years of commercial experience, specializing in software testing and quality assurance, software development, project management and team leadership. He is currently holding a position of a Team Leader at Goyello where he is mainly responsible for building and managing teams of professional developers and testers. He is also responsible for maintaining relations with customers and acquiring new ones, mainly through consultancy. He believes in Agile project management and he is a big fan of technology, especially Java related (but not limited too). He likes sharing knowledge about software development and practices, through his blog (blog.codeleak.pl) and Twitter (@kolorobot) but also on internal and external events like conferences or workshops.

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.

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you our best selling eBooks for FREE!

1. JPA Mini Book

2. JVM Troubleshooting Guide

3. JUnit Tutorial for Unit Testing

4. Java Annotations Tutorial

5. Java Interview Questions

6. Spring Interview Questions

7. Android UI Design

and many more ....

 

Leave a Reply

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

*


× six = 24

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Do you want to know how to develop your skillset and become a ...

Subscribe to our newsletter to start Rocking right now!

To get you started we give you our best selling eBooks for FREE!
Get ready to Rock!
To download the books, please verify your email address by following the instructions found on the email we just sent you.

THANK YOU!

Close