HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavaMinor

JUnit extending abstract test class

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
abstractjunittestclassextending

Problem

I have been thinking quite some time and asked an StackOverflow question about extending abstract test classes, but I haven't been able to do it until now.

I'll list my approach and then discuss some pros and cons and am asking you for a review of the code.

Consider the PlayerAction interface:

public interface PlayerAction {
    boolean isActionAllowed(final Player player);

    void performAction(final Player player) throws PlayerActionNotAllowedException;
}


In the invisible documentation I have written that in both methods that player != null must hold.

The abstract test class that should be extended, is the following:

@Ignore
public abstract class PlayerActionAbstractTest {
    static {
        assertTrue(true);
    }

    private final Supplier playerActionSupplier;

    private PlayerAction playerAction;

    public PlayerActionAbstractTest() {
        this.playerActionSupplier = null;
    }

    protected PlayerActionAbstractTest(final Supplier playerActionSupplier) {
        this.playerActionSupplier = Objects.requireNonNull(playerActionSupplier, "playerActionSupplier");
    }

    @Before
    final public void beforePlayerActionAbstractTest() {
        playerAction = playerActionSupplier.get();
    }

    @Test(expected = NullPointerException.class)
    public void testIsActionAllowedNullPlayer() {
        playerAction.isActionAllowed(null);
    }

    @Test(expected = NullPointerException.class)
    public void testPerformActionNullPlayer() {
        playerAction.performAction(null);
    }
}


An concrete implementation, I'll spare you all most irrelevant code:

```
public class AttackMonsterActionTest extends PlayerActionAbstractTest {
private Player self;
private Player opponent;

private final static PlayerConfiguration PLAYER_CONFIGURATION = new PlayerConfigurationBuilder()
.hitpoints(100)
.turnAction(p -> { })
.handCapacity(5)
.fieldMonsterCapacity(5)
.deck

Solution

You are not doing your tests 'appropriately', and this is part of your issue.

There is no need for the 'supplier'. The supplier is an instance field, and it's an extra level of abstraction that is unnecessary. JUnit has concepts at play that allows it to run tests in parallel. Having instance fields that are not part of the before/after system can lead to problems.

Instead of the supplier, you should have an abstract method, and your @Before method should be changed:

protected abstract PlayerAction supplyPlayerAction();

@Before
final public void beforePlayerActionAbstractTest() {
    playerAction = supplyPlayerAction();
}


Then, in your concrete class, instead of trying to beat the circular construction logic, all you need to do is to implement the concrete method supplyPlayerAction() like:

protected PlayerAction supplyPlayerAction() {
    return new AttackMonsterAction(0, 0, Player.createFromConfiguration(PLAYER_CONFIGURATION, "Opponent"))
}


This way the @Before things happen in the predictable order, etc.

In your pro/con terminology, you cannot implement a concrete class of the abstract class, without implementing the abstract method. The method is called in the @Begin in the Super Class, and the Super Class @Begins are called before the concrete class.

This is the way it should be done.

Code Snippets

protected abstract PlayerAction supplyPlayerAction();

@Before
final public void beforePlayerActionAbstractTest() {
    playerAction = supplyPlayerAction();
}
protected PlayerAction supplyPlayerAction() {
    return new AttackMonsterAction(0, 0, Player.createFromConfiguration(PLAYER_CONFIGURATION, "Opponent"))
}

Context

StackExchange Code Review Q#52108, answer score: 5

Revisions (0)

No revisions yet.