Home » Java » Calling a Static Singleton Class from Junit test throws StackOverflow error

Calling a Static Singleton Class from Junit test throws StackOverflow error

Posted by: admin October 26, 2017 Leave a comment

Questions:

When We run the DrawerOpenTest.java it throws the StackOverflow error, while we expect the test case to be passed. And when DrawerOpen class is singleton, assertThat(actualState, is(expectedState)); Should be also true in test case.

Note that ‘State’ is interface with 3 basic methods.

DrawerOpenTest class

import static org.hamcrest.CoreMatchers.*;
import org.junit.*;

public class DrawerOpenTest {

@Test
public void openCloseButtonPushedPositiveTest(){
    DVDPlayer cut = DVDPlayer.getInstance(DrawerOpen.getInstance());
    State expectedState = DrawerClosedNotPlaying.getInstance();
    State actualState = cut.openCloseButtonPushed();
    assertThat(actualState, is(sameInstance(expectedState)));
}
}

DrawerOpen Class

public class DrawerOpen implements State {

private DVDPlayer player = DVDPlayer.getInstance(DrawerOpen.getInstance());

private static State state;

private DrawerOpen() {}

public static State getInstance() {
    if(state == null)
        state = new DrawerOpen();
    return state;
}


@Override
    public void openCloseButtonPushed() {
        player.close();
        player.changeState(DrawerClosedNotPlaying.getInstance());
    }

@Override
public void playButtonPushed() {
player.close();
player.play();
player.changeState(DrawerClosedPlaying.getInstance());

}

@Override
public void stopButtonPushed()
}

}

DVDPlayer Class

public class DVDPlayer {
private DVDPlayer() {}
private static DVDPlayer player = null;

private State state;

public State getState() {
    return state;
}

public static DVDPlayer getInstance(State stateParam) {
    //making it singleton
    if(player == null)
    {   
        player = new DVDPlayer();
        player.state = DrawerClosedNotPlaying.getInstance();
    }
    else 
        player.state = stateParam;
    return player;      
}

public void changeState(State newState) {
    this.state=newState;        
}
public State openCloseButtonPushed(){
    state.openCloseButtonPushed();
    return state;
}

public State playButtonPushed() {
    state.playButtonPushed();
    return state;
}

public State stopButtonPushed() {
    state.stopButtonPushed();
    return player.state;
}
public void open() {
    System.out.println("DVDPlayer is opening.....");
}
public void close() {
    System.out.println("DVDPlayer is closing.....");

}
public void play() {
    System.out.println("DVDPlayer is playing.....");
}   
public void stop() {
    System.out.println("DVDPlayer is stopping.....");
}
}

Result: It results the StackOverflowError as its keeps initializing itself. Please help how to get this test as positively passed.

java.lang.StackOverflowError
    at DrawerOpen.<init>(DrawerOpen.java:5)
    at DrawerOpen.getInstance(DrawerOpen.java:13)
    at DrawerOpen.<init>(DrawerOpen.java:5)
    at DrawerOpen.getInstance(DrawerOpen.java:13)
    at DrawerOpen.<init>(DrawerOpen.java:5)
    at DrawerOpen.getInstance(DrawerOpen.java:13)
    at DrawerOpen.<init>(DrawerOpen.java:5)
    at DrawerOpen.getInstance(DrawerOpen.java:13)
    at DrawerOpen.<init>(DrawerOpen.java:5)
    at DrawerOpen.getInstance(DrawerOpen.java:13)
    at DrawerOpen.<init>(DrawerOpen.java:5)
    at DrawerOpen.getInstance(DrawerOpen.java:13)
    at DrawerOpen.<init>(DrawerOpen.java:5)
    at DrawerOpen.getInstance(DrawerOpen.java:13)
    at DrawerOpen.<init>(DrawerOpen.java:5)
    at DrawerOpen.getInstance(DrawerOpen.java:13)
    at DrawerOpen.<init>(DrawerOpen.java:5)
Answers:

This:

public class DrawerOpen implements State {

  private DVDPlayer player = DVDPlayer.getInstance(DrawerOpen.getInstance());

  // ...
}

means that you will call DrawerOpen.getInstance() every time you try to create a DrawerOpen, because player is a member variable.

In order to create an instance in DrawerOpen.getInstance(), you’ve got to create an instance of DrawerOpen; but that calls DrawerOpen.getInstance() again, requires you to create a DrawerOpen, which calls DrawerOpen.getInstance()

You can avoid this recursive call by assigning DrawerOpen.getInstance() to a static field:

public class DrawerOpen implements State {
  private static DrawerOpen INSTANCE = DrawerOpen.getInstance();

  private DVDPlayer player = DVDPlayer.getInstance(INSTANCE);

  // ...
}

However, there is then another problem, which is that you won’t have assigned INSTANCE at the time you call DVDPlayer.getInstance(INSTANCE), so you will end up calling DVDPlayer.getInstance(null).

One way to solve this would be to call:

  private DVDPlayer player = DVDPlayer.getInstance(this);

However, this is an example of what is called unsafe publication, wherein you are leaking the this reference before the DrawerOpen class is fully initialized. This may lead to further unexpected problems.


Your code is a bit of a tangled mess. You should think carefully about why you are using singletons at all, because these don’t seem like singleton properties to me.

For example, it’s a bit dodgy you’ve got a “singleton” which depends upon a parameter: if you call the DVDPlayer.getInstance with a different State instance, you currently ignore the new state instance, and use the previous one. This would very likely lead to confusing or surprising behavior: I gave you this state instance, but the DVDPlayer is using that state instance.

Questions:
Answers:

Change these two lines:

private DVDPlayer player = DVDPlayer.getInstance(DrawerOpen.getInstance());

private DrawerOpen() {}

to this:

private DVDPlayer player;

private DrawerOpen() {
    player = DVDPlayer.getInstance(this);
}

This will get rid of the recursive initialization loop.

Explanation: this in a constructor refers to the object being constructed. This is the singleton that would be returned by DrawerOpen.getInstance() … if the object had been constructed at that point in the code.