使用Mockito注入Mock Spring Repository时出现空指针异常
The Problem
I'm trying to test my DefaultRuleTester class, which has an @Autowired RuleChainRepository field. Every time I run the test, I get a NullPointerException pointing to that autowired field. I've tried using MockitoJUnitRunner and MockitoAnnotations.initMocks(this), but neither has fixed the issue.
Code Snippets
Under Test: DefaultRuleTester
@Component public class DefaultRuleTester implements RuleTester { private static final Logger LOG = LogManager.getLogger(DefaultRuleTester.class); @Autowired RuleChainRepository ruleChainRepository; public DefaultRuleTester() { } @Override public List<ResultChain> test(Entity entity) { if (entity == null) return Collections.emptyList(); try { return selectAndTestAllRuleChains(entity); } catch (InvalidClassException e) { LOG.error("Unable to select RuleChains from database.", e); return Collections.emptyList(); } } protected List<ResultChain> selectAndTestAllRuleChains(Entity entity) throws InvalidClassException { Collection<RuleChain> ruleChains = selectRuleChainsFromConnection(); return testRuleChains(entity, ruleChains); } private Collection selectRuleChainsFromConnection() throws InvalidClassException { Collection<RuleChain> ruleChains = ruleChainRepository.findAll(); if (ruleChains == null) return Collections.emptyList(); return ruleChains; } protected List<ResultChain> testRuleChains(Entity entity, Collection<RuleChain> ruleChains) { // business logic } }
Repository Definition: RuleChainRepository
public interface RuleChainRepository extends JpaRepository<RuleChain, Long> { }
Test Class: DefaultRuleTesterTest
@RunWith(MockitoJUnitRunner.class) public class DefaultRuleTesterTest { private final Incident entity = new Incident(); @InjectMocks private DefaultRuleTester tester; @Mock private RuleChainRepository ruleChainRepository; @Before public void setUp() { tester = new DefaultRuleTester(); } @Test public void whenEntityTested_shouldReturnGivenResultChains() { Mockito.when(ruleChainRepository.findAll()).thenReturn(singletonList(new RuleChainSpy())); List<ResultChain> resultChains = tester.test(entity); assert(1, resultChains.size()); } }
Error Stack Trace
java.lang.NullPointerException at com.remsdaq.resque.officersubscription.entity_controller.DefaultRuleTester.selectRuleChainsFromConnection(DefaultRuleTester.java:30) at com.remsdaq.resque.officersubscription.entity_controller.DefaultRuleTester.selectAndTestAllRuleChains(DefaultRuleTester.java:25) at com.remsdaq.resque.officersubscription.entity_controller.DefaultRuleTester.test(DefaultRuleTester.java:17) at com.remsdaq.resque.officersubscription.entity_controller.DefaultRuleTesterTest.whenTested_shouldReturnGivenResultChains(DefaultRuleTesterTest.java:20) 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:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) 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:325) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.mockito.internal.runners.DefaultInternalRunner$1.run(DefaultInternalRunner.java:79) at org.mockito.internal.runners.DefaultInternalRunner.run(DefaultInternalRunner.java:85) at org.mockito.internal.runners.StrictRunner.run(StrictRunner.java:39) at org.mockito.junit.MockitoJUnitRunner.run(MockitoJUnitRunner.java:163) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
The Fix
Your issue is in the setUp method! You're manually creating a new DefaultRuleTester instance, which overwrites the one that Mockito already created and injected with your mock RuleChainRepository.
Step-by-Step Fix
- Remove the manual instance creation in
setUp: Delete the linetester = new DefaultRuleTester();. Mockito will automatically create thetesterinstance and inject the mock repository for you. - Fix the assertion syntax: Replace
assert(1, resultChains.size());withassertEquals(1, resultChains.size());— the original syntax is invalid for Java's assert statement.
Here's the corrected test class:
@RunWith(MockitoJUnitRunner.class) public class DefaultRuleTesterTest { private final Incident entity = new Incident(); @InjectMocks private DefaultRuleTester tester; @Mock private RuleChainRepository ruleChainRepository; // Removed problematic setUp method @Test public void whenEntityTested_shouldReturnGivenResultChains() { Mockito.when(ruleChainRepository.findAll()).thenReturn(Collections.singletonList(new RuleChainSpy())); List<ResultChain> resultChains = tester.test(entity); assertEquals(1, resultChains.size()); } }
Alternative Approach (If You Prefer Manual Instance Creation)
If you want to keep creating the instance manually, you'll need to inject the mock yourself using reflection (since Mockito can't inject into manually created instances automatically):
public class DefaultRuleTesterTest { private final Incident entity = new Incident(); private DefaultRuleTester tester; @Mock private RuleChainRepository ruleChainRepository; @Before public void setUp() { MockitoAnnotations.initMocks(this); tester = new DefaultRuleTester(); // Manually inject the mock using Spring's ReflectionTestUtils ReflectionTestUtils.setField(tester, "ruleChainRepository", ruleChainRepository); } // Test method remains the same }
The first approach (letting Mockito handle instance creation) is cleaner and recommended.
内容的提问来源于stack exchange,提问作者Chris

