Question

I would like to write unit tests for a singleton class, but this class have dependencies to ui components. The class is PageManager and have some functionality to go back and go forward in the page history. With a unit test I like to test this history functionality but I don't want to initialize the ui stuff, becouse it is not needed for this test.

I'am new to JMockit and I tryed this out, but wihtout success: Here is the original class to be mocked:

public final class PageManager {
  private static final PageManager INSTANCE = new PageManager();

  private final Set<Page> pages = new HashSet<>();
  private Page currentPage;
  private boolean initialized = false;

  private PageManager() {
    // Do some UI stuff
  }

  public static PageManager getInstance() {
    return INSTANCE;
  }

  public void addPage(final Page page) {
    pages.add(page);
  }

  public void initialize() {
    // Do some UI stuff
    initialized = true;
  }

  public Page getPage() { return currentPage; }
  public void setPage(final Page page) { ... }

  public void goBack() { ... };
  public void goForward() { ... };
  public boolean canGoBack() { ... };
  public boolean canGoForward() { ... };

  private void activatePage(final Page page) {
    // Do some UI stuff
    this.currentPage = page;
  }

  private void deactivatePage(final Page page) {
    // Do some UI stuff
  }
}

This is the mocked version:

public final class MockedPageManager extends MockUp<PageManager> {
  PageManager instance;

  @Mock
  void $init(final Invocation invocation) {
    instance = invocation.getInvokedInstance();
  }

  @Mock
  void initialize() {
    // Don't do UI stuff
    Deencapsulation.setField(instance, "initialized", true);
  }

  @Mock
  void activatePage(Page page) {
    Deencapsulation.setField(instance, "currentPage", page);
    page.activate();
  }

  @Mock
  void deactivatePage(Page page) {
  }
}

And a small test:

@Test
public void testGoBack() {
  new MockedPageManager();

  final Page p1 = new Page() { @Override public String getTitle() { return "p1"; } };
  final Page p2 = new Page() { @Override public String getTitle() { return "p2"; } };

  final PageManager pm = PageManager.getInstance();
  pm.addPage(p1);
  pm.addPage(p2);
  pm.initialize();

  pm.setPage(p1)
  assertEquals(p1, pm.getCurrentPage());
  pm.setPage(p2);
  assertEquals(p2, pm.getCurrentPage())
  assertTrue(pm.canGoBack());
  pm.goBack();
  assertEquals(p1, pm.getCurrentPage());
}

In this test, the $init method get invoked by JMockit correctly. The problem is, that an NullPointerExceptions is thrown in the test when pm.addPage(p1) is called. The stacktrace says, that the NPE occurs in the original class PageManager becouse the field Set pages is null.

My question is: Is this singleton class correctly mocked? Does the $init method override only the constructor or also the Java instance initializer i.e. Set pages = new HashSet<>();

Was it helpful?

Solution

As stated here, instance initializing blocks or statements are copied into each constructor (by the compiler). I suspect that JMockit uses reflection/byte code manipulation to mock the class's constructor, effectively circumventing all of the initializing code. Therefore the initializers are not executed and the set variable remains null. If you really have to make this work, try to initialize it properly in the mock. Better yet, refactor your class to allow its use in tests (e.g. an additional package private constructor for testing with injected dependencies; or move the page history functionality to its own class).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top