Java – why do void methods get NullPointerException on mock objects?
I am writing unit tests for my java application, which analyzes the submission to stash, an atlas application similar to GitHub
The method I am testing is as follows:
public List<Message> processEvent(RepositoryRefsChangedEvent event) { ArrayList<Message> commitList = new ArrayList<Message>(); for (RefChange refChange : event.getRefChanges()) { LOGGER.info("checking ref change refId={} fromHash={} toHash={} type={}",refChange.getRefId(),refChange.getFromHash(),refChange.getToHash(),refChange.getType()); if (refChange.getRefId().startsWith(REF_BRANCH)) { if (refChange.getType() == RefChangeType.ADD && isDeleted(refChange)) { LOGGER.info("Deleted a ref that never existed. This shouldn't ever occur."); } else if (isDeleted(refChange) || isCreated(refChange)) { branchCreation(refChange,event.getRepository(),commitList); } else { sepCommits.findCommitInfo(refChange,commitList); } } else { refNotProcessed(refChange); } } return commitList; }
I try to make sure that if I have a git notes commit, I ignore the processing and call refnotprocessed (..)
Fortunately, I can solve this problem relatively easily and come up with the following solutions:
@RunWith (MockitoJUnitRunner.class) public class RefChangEventTest { @Mock RefChange ref; @Mock RepositoryRefsChangedEvent refsChangedEvent; @Mock Repository repo; @Mock ApplicationPropertiesService appService; @Mock SEPCommits sepCommits; @Spy SEPRefChangeEventImpl sepRefChangeEvent = new SEPRefChangeEventImpl(sepCommits,appService); @Before public void testSetup() { Collection<RefChange> refList = new ArrayList<RefChange>(1); refList.add(ref); when(refsChangedEvent.getRefChanges()).thenReturn(refList); when(refsChangedEvent.getRepository()).thenReturn(repo); } @Test public void gitNotesAreIgnored() throws Exception { when(ref.getRefId()).thenReturn("refs/notes/foo"); when(ref.getFromHash()).thenReturn("da69d7e202d7f66cba01c6f4030bd5975adbf200"); when(ref.getToHash()).thenReturn("da69d7e202d7f66cba01c6f4030bd5975adbf201"); doNothing().when(sepCommits).findCommitInfo(any(RefChange.class),any(Repository.class),any(ArrayList.class)); sepRefChangeEvent.processEvent(refsChangedEvent); verify(sepRefChangeEvent,times(1)).refNotProcessed(ref); }
After that, if I change the ref name to refs / heads / Foo and so on, I want to see if my unit test will fail for the right reason I want to see something like this: 1 is expected to execute refnotprocessed, but it doesn't run at all
Instead, I get:
java.lang.NullPointerException at com.cray.stash.SEPRefChangeEventImpl.processEvent(SEPRefChangeEventImpl.java:62) at ut.com.isroot.stash.plugin.RefChangEventTest.gitNotesAreIgnored(RefChangEventTest.java:48) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) at org.junit.runners.ParentRunner.run(ParentRunner.java:309) at org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37) at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62) at org.junit.runner.JUnitCore.run(JUnitCore.java:160) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:117) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:42) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:253) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:84) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Where it points to sepcommits findCommitInfo(..) By default, the method with void signature does not perform any operation when simulating the call That's what I want it to do I want it to be called, but do nothing, just record the fact that sepcommits interacts with it Why does NPE happen?
Here are more ways people ask for:
public SEPRefChangeEventImpl(SEPCommits sepCommits,ApplicationPropertiesService appService) { this.sepCommits = sepCommits; try { endpoint = appService.getPluginProperty("plugin.fedmsg.events.relay.endpoint"); } catch (Exception e) { LOGGER.error("Failed to retrieve properties\n" + e); } if (endpoint == null) { endpoint = "tcp://some.web.address" } } public void refNotProcessed(RefChange refChange) { LOGGER.info("This type of refChange is not supported.\n refId={} fromHash={} toHash={} type={}",refChange.getType()); } public void findCommitInfo(RefChange ref,Repository repo,ArrayList<Message> commitList) { Page<Commit> commits = getChangeset(repo,ref); for (Commit commit : commits.getValues()) { String topic = topicPrefix + repo.getProject().getKey() + "." + repo.getName() + ".commit"; Message message = new Message(getInfo(commit,ref),topic); commitList.add(message); } }
Solution
As I can see from your settings, sepcommits is a dependency in the class that contains the processEvent () method
You must inject the simulation you created in the test into the seprefchangeevent variable Typically, this can be done by passing it as a parameter during construction or through a setter method I can't see such code in your test class I think you're actually hitting a real instance instead of a simulated instance there, which leads to an exception