I have a @Service
called UserServiceImpl
that depends on two other beans. One is the UserRepository
bean and the other is a bean called SessionService
.
My requirement is during tests of the UserServiceImpl class, I must be able to inject a mock of the SessionService
dependency, but keep the UserRepository
dependency as it is.
My service class that looks like this:
@Service
@Slf4j
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private SessionService sessionService;
@Override
public User create(User user) {
log.info("User Creation at Service");
// ... Do some validations .. //
// This needs to be mocked in Unit Tests
String returnValue = sessionService.doSomethingThatIDontWantInTests();
user.setInternalKey(returnValue);
// .. Do some more Validations .. //
return userRepository.save(user);
}
}
Now, here is my Test Class:
@SpringBootTest
class UserServiceTest {
@InjectMocks
private UserServiceImpl userService;
@Mock
private SessionService sessionService;
@Test
void CreateUserTest() {
Mockito.when(sessionService.doSomethingThatIDontWantInTests()).thenReturn("abcxyz123321");
User user = new User();
user.setName("John Doe");
user.setEmail("[email protected]");
User savedUser = userService.create(user);
assertNotNull(savedUser.getUserId());
}
}
When I run this test, Mockito successfully mocks the SessionService
call. But, UserServiceImpl.createUser()
still fails with the message saying:
java.lang.NullPointerException: Cannot invoke "com.myproject.data.repos.UserRepository.save(User)" because "this.userRepository" is null
Should I inject UserRepository
also as a mock and use Mockito to mock the UserRepository.save()
method?
I would like to mock only the SessionService
dependency, and not the UserRepository
dependency.
Is it something doable? If so, how? Please advise.
Thanks, Sriram
CodePudding user response:
You are mixing different testing styles.
Style 1 - spring integration test
This is when Spring Boot creates the beans in its context, and you inject them to your test class.
- use
@SpringBootTest
- use
@Autowired
to inject beans to your test - use
@MockBean
to replace beans in Spring contexts by mocks
Style 2 - Unit test
This does not use Spring DI. In this style, it is typical to mock all dependencies.
- use
@ExtendWith(MockitoExtension.class)
- annotate dependencies as
@Mock
- annotate SUT with
@InjectMocks
Using real dependencies is also possible, but in that case you need to construct SUT manually - Mockito does not support partial injections.
Unit tests tend to be much more lightweight and less brittle, on the other hand integration tests cover a large chunk of the app.
Note that if UserRepository
is a spring-data repo, it cant be created manually.
CodePudding user response:
You must use a @SpyBean
(https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/mock/mockito/SpyBean.html), which will inject the original bean but which you can verify the calls and arguments.