0

For some unexplainable reason a field (currentExp) I'm assigning in the constructor isn't correctly set while using a mock for my unittest. I'm assigning the field currentExp by loading it via the loadExperience method using my Storage class (which uses SharedPreferences). When I'm unittesting this, I'd like to mock the Storage class, so loadexperience returns a value of 10.

Here's my concrete Experience class:

public class Experience extends StorageObject {

    private int currentExp = 0;

    public Experience() {
        this(new Storage());
    }

    @VisibleForTesting
    protected Experience(Storage storage) {
        super(storage);
    } // Debug point #2

    @Override
    protected void init(Storage storage) {
        this.currentExp = storage.loadExperience();
    } // Debug point #1
}

It extends StorageObject:

public abstract class StorageObject {
    protected Storage storage;

    protected StorageObject() {
        this(new Storage());
    }

    @VisibleForTesting
    protected StorageObject(Storage storage) {
        this.storage = storage;
        init(storage);
    }

    protected abstract void init(Storage storage);
}

And this is my unittest:

@Test
public void testConstructor_StorageValuePositive_IsSetAsCurrentExp() {
    int expectedSavedExp = 10;
    Storage storageMock = mock(Storage.class);
    doReturn(expectedSavedExp).when(storageMock).loadExperience();

    Experience exp = new Experience(storageMock);

    assertEquals(expectedSavedExp, exp.getCurrentExp());
}

While debugging I found out the mock DOES work, and the value of 10 is assigned to currentExp at Debug point #1. Then shortly afterwards, at Debug point #2 the value seems to be 0 again.

Anyone got a clue what is happening here, and how to solve this problem?

P Kuijpers
  • 1,593
  • 15
  • 27

1 Answers1

1

The problem here is the initialization order. The super constructor happens first then the field initialization.

So your constructor sets the currentExp in it's super call to 10 then the field gets initilized with 0.

So what can you do? Some ideas: Move the currentExp to the parent class or don't give it a default value.

More reading material:

http://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.5

https://stackoverflow.com/a/14806340/5842844

Community
  • 1
  • 1
jbarat
  • 2,382
  • 21
  • 23
  • Great, thanks! I guess you mean `currentExp` instead of `expectedSavedExp`? ;-) But I got the idea how to solve it, that's what matters. ... Another idea: because I've set `storage` as a field in `StorageObject`, I could also initialize `currentExp` with `storage.loadExperience()` as default value, instead of assigning it in the constructor. In that way I can also leave out the `abstract init` method. That appears to solve the unittest problem, but is it common / correct to do so? – P Kuijpers Jul 18 '16 at 16:12
  • ups yes I meant currentExp. You could just move the init into the child class there is no reason for that to be on the parent. – jbarat Jul 18 '16 at 16:58