When it comes to Mockito and JUnit testing, one of the most common issues developers face is the infamous “null @MockBean” problem. This article dives into the root cause of this issue and offers a solution to ensure your tests run smoothly and efficiently. Grab your coffee, and let’s get started!
Understanding the Problem
The crux of the issue lies in the beans mocked by @MockBean
being null during runtime. To illustrate this, let’s take a look at a typical scenario where a developer tries to mock an @Autowired
bean using @MockBean
but encounters a NullPointerException
at runtime.
Class Under Test
@Service
public class UserServiceImpl {
@Autowired
GenericRestClient restClient;
@Autowired
RequestMapper requestMapper;
@Autowired
ResponseMapper responseMapper;
@Override
public List<User> findUsers(RRequest requestBody,
String index) throws HostException {
List<User> users = requestMapper.mapRequest("test");
// ...doing something
return users;
}
}
The Test Class
// ...
@RunWith(MockitoJUnitRunner.class)
public class UserServiceImplTest {
@MockBean
GenericRestClient restClient;
@MockBean
RequestMapper requestMapper;
@MockBean
ResponseMapper responseMapper;
@InjectMocks
UserServiceImpl userService;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testFindUsers() {
List<Users> users = null;
Mockito.when(requestMapper.mapRequest("test"))
.thenReturn(users);
assertNull(users);
}
}
Diagnosing the Issue
As mentioned in a comment on the original question, the problem stems from the fact that @MockBean
is a Spring annotation, while @InjectMocks
and @Mock
are Mockito annotations. Mockito on its own does not know anything about Spring, which is why the annotated objects are null.
The Solution
Rather than using the Spring Runner to run the test (which can be resource-intensive and more suitable for integration testing), we recommend refactoring the UserServiceImpl
to make dependencies more visible. This is achieved by using constructor injection instead of field injection.
Refactored UserServiceImpl Class
@Service
public class UserServiceImpl {
private final GenericRestClient restClient;
private final RequestMapper requestMapper;
private final ResponseMapper responseMapper;
@Autowired // in recent spring version, this annotation can be omitted
public UserServiceImpl(GenericRestClient restClient, RequestMapper requestMapper, ResponseMapper responseMapper) {
this.restClient = restClient;
this.requestMapper = requestMapper;
this.responseMapper = responseMapper;
}
// ...
}
Refactored Test Class
// ...
@RunWith(MockitoJUnitRunner.class)
public class UserServiceImplTest {
@Mock
GenericRestClient restClient;
@Mock
RequestMapper
@Mock
ResponseMapper responseMapper;
UserServiceImpl userService;
@Before
public void init() {
userService = new UserServiceImpl(restClient, requestMapper, responseMapper);
}
// ...
}
With this approach, there’s no need for @InjectMocks
anymore. The refactored test class now uses Mockito’s @Mock
annotation instead of Spring’s @MockBean
, which resolves the issue of null beans.
Conclusion
In conclusion, the key to resolving the null @MockBean issue lies in understanding the difference between Spring and Mockito annotations and utilizing constructor injection to make dependencies more visible. This not only solves the problem at hand but also ensures that your tests run more efficiently, keeping them lightweight and focused on their purpose as unit tests.
So, the next time you encounter a similar problem, keep these tips in mind, and you’ll be well on your way to creating efficient and effective tests!