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
@Autowiredto inject beans to your test - use
@MockBeanto 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.
