Reputation: 2597
This is my first junit tests using Mockito. I'm facing the issue of NPE for the service that was used in @InjectMocks. I looked at the other solutions, but even after following them, it shows same. Here is my code.
@RunWith(MockitoJUnitRunner.class)
public class CustomerStatementServiceTests {
@InjectMocks
private BBServiceImpl bbService;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
/**
* This test is to verify SUCCESS response
*/
@Test
public void testSuccess() {
BBResponse response = bbService.processDetails(txs);
assertEquals("SUCCESSFUL" ,response.getResult());
}
}
@Service
public class BBServiceImpl implements BBService {
final static Logger log = Logger.getLogger(BBServiceImpl.class);
public BBResponse process(List<Customer> customers) {
// My business logic goes here
}
}
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.23.4</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
My "bbService" object is null here
Am I missing anything here?
Upvotes: 4
Views: 11858
Reputation: 4259
After a discussion which provided additional informations, the answer can be summarized as a maven configuration problem between junit4
and junit5
.
java.lang.NullPointerException
at com.cts.rabo.CustomerStatementServiceTests.testSuccess(CustomerStatementServiceTests.java:83)
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.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:675)
at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
...
The stacktrace showed a clear usage of the junit5
engine.
The pom also included the following dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.2.5.RELEASE</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
Before Spring Boot 2.2.0.RELEASE, spring-boot-starter-test included junit4 dependency transitively. Spring Boot 2.2.0 onwards, Junit Jupiter is included instead.
And according to this answer the exclusion seems to prevent the execution.
Removing the exlusion solved the problem for me.
I recommend a switch to junit5
if there are no apparent requirements to use junit4
.
Check this answer for more infos on how to use mockito
together with junit5
.
Upvotes: 3
Reputation: 42431
I think you misuse the @InjectMocks
annotation
It makes sense if you have a real class (usually the one you want to test) that has dependencies, which are initialized with constructor injection or setter injection.
BBServiceImpl
on the other hand has no real dependencies.
Now, you could create mocks for those dependencies manually and then create an instance of the class under test or let mockito identify the way to create the class and "inject" the mocks for you. You'll still want to create mocks by means of using @Mock
annotation because you do want to specify expectations during the test.
Here is an example of how it should look like:
public interface BBService {
int process(String s); // simplified but still illustrates the concept
}
public class BBServiceImpl implements BBService {
//here is a dependency that should be mocked when we will test BBServiceImpl class
private SizeCalculator sizeCalculator;
// I use constructor injection here
public BBServiceImpl(SizeCalculator sizeCalculator) {
this.sizeCalculator = sizeCalculator;
}
public int process(String s) {
// here is a dependency invocation
return sizeCalculator.calculateSize(s);
}
}
As you see, the implementation has a real dependency that can be mocked during the test.
Now, this dependency looks like:
public interface SizeCalculator {
int calculateSize(String s);
}
public class SizeCalculatorImpl implements SizeCalculator {
public int calculateSize(String s) {
return s.length();
}
}
Ok, now lets create a test for the BBServiceImpl
. We don't want to create a SizeCalculatorImpl
and prefer to supply a mock for it and specify an expectation on the runtime generated proxy created out of the interface SizeCalcluator
:
@RunWith(MockitoJUnitRunner.class)
public class MockitoInjectMocksTest {
// I need a reference on my mock because I'll specify expectations during the tests
@Mock
private SizeCalculator sizeCalculator;
@InjectMocks
private BBServiceImpl underTest;
@Test
public void testInjectMocks() {
Mockito.when(sizeCalculator.calculateSize("hello")).thenReturn(5);
int actual = underTest.process("hello");
assertEquals(actual, 5);
}
}
Note the line
@InjectMocks
private BBServiceImpl underTest;
Mockito will find the constructor and inject the mock
Of course in this case we could do it manually:
@RunWith(MockitoJunitRunner.class)
public class SampleManualTest {
@Mock
private SizeCalculator sizeCalc;
private BBServiceImpl underTest;
@Before
public void init() {
this.underTest = new BBServiceImpl(sizeCalc);
}
}
But think about the case where there are many dependencies in class BBServiceImpl
, InjectMocks can be more convenient in this case + if the setter injection is used, this way can save explicit calls to the setters.
Upvotes: 2