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<>();
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).