Why i can't save anything in my mocked repository - java

I'm learning testing in spring boot, i have this test:
#ExtendWith(MockitoExtension.class)
#SpringBootTest
class AppUserServiceTest {
#Mock
private UserRepository repository;
private UserService userService;
#BeforeEach
void setUp() {
userService = new UserService(repository);
}
#Test
void itShouldLoadUsernameByName() {
AppUser appUser = new AppUser(
ApplicationRole.USER,
"leonardo",
"rossi",
"leo__",
"a#gmail.com",
"password"
);
repository.save(appUser);
userService.loadUserByUsername(appUser.getUsername());
verify(repository).searchByUserName(appUser.getUsername());
}
}
it keep throwing me an exception because it doesn't find any user in the database, in fact even if i save the user the repository.findAll() return 0 element.
This is my User service that i'm trying to test:
#AllArgsConstructor
#Service
public class UserService
implements UserDetailsService {
private final UserRepository repository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return repository.searchByUserName(username)
.orElseThrow(
()-> new UsernameNotFoundException("The username " + username + " has not been found")
);
}
This is my H2 databse for testing configuration:
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

A mock is just a mock. It's never actually doing something. It just can behave like a real thing, but you have to tell the mock how to do it.
So if you mock the UserRepository and try to save something nothing will happen. Because again, a mock never actually does something. The advantage of this is that you don't actually need the whole infrastructure to unit test a tiny method. Additionally the test gets independent of data. What you can do now is return some data if some method is called.
In your case, you don't need a whole database to test the functionality of the UserService. Thus mocking the repository is a good option. Now since it's a mock you have to tell the mock how to behave for your test case:
#Test
void itShouldLoadUsernameByName() {
AppUser appUser = new AppUser(
ApplicationRole.USER,
"leonardo",
"rossi",
"leo__",
"a#gmail.com",
"password"
);
when(standortRepository.searchByUserName(any())).thenReturn(appUser);
userService.loadUserByUsername(appUser.getUsername());
//More expectations ...
verify(repository).searchByUserName(appUser.getUsername());
}
Now what I have done is to tell the mock it should return the appUser, when the Method loadByUsername is called.
P.s. these are the static imports I used:
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

Related

My service test fails when I have an if statement in the class under test SpringBoot

I am writing tests from my springboot application. The class has a method getUserById which returns Optional<User>. This methos has an if statement that will check whether an row was returned from repository before sending a response.
Problem:
With the if statement in place, my test always throws the error in the if statement. when I remove the if statement, the test passes. What am I missing?
This is my UserServiceImpl (Class under test)
#Service
#RequiredArgsConstructor
#Transactional
#Slf4j
public class UserServiceImpl implements UserService, UserDetailsService {
#Autowired
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
#Override
public List<User> getUsers() {
log.info("Fetching users");
return userRepository.findAll();
}
#Override
public Optional<User> getUserById(Long id) {
log.info("Fetching user id: {}", id);
Optional<User> user = userRepository.findById(id);
if (!user.isPresent()) {
throw new ResourceNotFoundException(MessageUtil.ERROR_USER_NOTFOUND);
}
return user;
}
}
This is my UserServiceImplTest (test class)
#RunWith(SpringRunner.class)
#SpringBootTest
class UserServiceImplTest {
#MockBean
private UserRepository userRepositoryTest;
#InjectMocks
private UserServiceImpl userServiceTest;
#Mock
private PasswordEncoder passwordEncoder;
private List<User> userSet;
private User user1;
private User user2;
#BeforeEach
void setUp() {
userServiceTest = new UserServiceImpl(userRepositoryTest, passwordEncoder);
Set<ApplicationUserRole> roles = new HashSet<>();
roles.add(ApplicationUserRole.TEST_USER);
userSet = new ArrayList<>();
user1 = User.builder().nickname("test-nickname")
.id(1L)
.username("254701234567")
.roles(roles)
.password("password")
.build();
user2 = User.builder().nickname("test2-nickname2")
.id(2L)
.username("254701234589")
.roles(roles)
.password("password")
.build();
userSet.add(user1);
userSet.add(user2);
userSet.stream().forEach(user -> {
userServiceTest.saveUser(user);
});
}
#AfterEach
void tearDown() {
}
#Test
void testGetUsers() {
when(userServiceTest.getUsers()).thenReturn(userSet);
assertEquals(2, userServiceTest.getUsers().size());
verify(userRepositoryTest).findAll();
}
#Test
void testGetUserById() {
when(userServiceTest.getUserById(user1.getId())).thenReturn(Optional.ofNullable(user1));
assertEquals(1, user1.getId());
verify(userRepositoryTest).findById(user1.getId());
}
#Test
void testSaveUser() {
when(userServiceTest.saveUser(user1)).thenReturn(user1);
assertEquals(1L, user1.getId());
verify(userRepositoryTest).save(user1);
}
#Test
void updateUser() {
user1.setNickname("nickname-update");
when(userServiceTest.saveUser(user1)).thenReturn(user1);
assertEquals("nickname-update", user1.getNickname());
verify(userRepositoryTest).save(user1);
}
}
NOTE: Other tests work just fine
None of your tests set up the repository mock. You are trying to mock the service method instead, which will implicitly call the real method while mocking. But the service method is never called to assert correct behavior. In other words: your service's behavior is never exercised by the test, because the return value of the method calls is overwritten.
Example:
#Test
void testGetUsers() {
// repository is never mocked
// vvvvvvvvvvvvvvvvvvvvvvvvvv--- this calls the service method
when(userServiceTest.getUsers()).thenReturn(userSet);
// ^^^^^^^^^^^^^^^^^^^--- this overwrites the return value of the service method
assertEquals(2, userServiceTest.getUsers().size()); // this uses the overwritten return value
verify(userRepositoryTest).findAll();
}
To fix, you need to mock the repository (not the service) and then call the real service. It is also quite useless to assert the user's id, because the user is set up by the test, not in the classes under test.
#Test
void testGetUserById() {
// arrange
when(userRepositoryTest.getUserById(user1.getId())
.thenReturn(Optional.ofNullable(user1));
// act
Optional<User> userById = userServiceTest.getUserById(user1.getId());
// assert
assertEquals(1, user1.orElseThrow().getId());
verify(userRepositoryTest).findById(user1.getId());
}
I'd also question your usage of verify at the end of test. You are testing implementation details here. You should only be interested in the return value of your service, not which methods of the repository it is calling. Especially since you are mocking those methods anyway, so you already know with which arguments they are called; otherwise the mock would not return the configured return value in the first place.
A side-question is why your if-condition is always true and the exception always thrown. Again: incorrect/missing setup of your mocks.
#Test
void testGetUserById() {
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv--- calls real method, but the repository mock is not set up
when(userServiceTest.getUserById(user1.getId()))
.thenReturn(Optional.ofNullable(user1));
assertEquals(1, user1.getId());
verify(userRepositoryTest).findById(user1.getId());
}
You are trying to mock the service method call but the service is autowired — that doesn’t work. You can mock instead the repository method call since the repository is annotated as MockBean, that should work.

How do I mock an optional class in java?

I was mocking my crud app and I was trying to mock the update API. As findById() method belongs to Optional class, I can't find a way to mock it. Here is the code snippet.
#Mock
#Autowired
private UserRepository repo;
#Test
public void testUpdateUser() {
Integer userId = 3;
Optional<User> optionalUser = repo.findById(userId);
User user = optionalUser.get();
user.setFirstName("Paul");
repo.save(user);
User updatedUser = repo.findById(userId).get();
Assertions.assertThat(updatedUser.getFirstName()).isEqualTo("Paul");
}
Optional class is a final class, so you cannot mock this class with Mockito. You should use PowerMockito. I add below a sample code. Please try this.
#RunWith(PowerMockRunner.class)
#PrepareForTest(Optional.class)
public class PowerMockitoExample {
#Mock
Optional<User> optionalMock;
#Mock Repository repo;
#Test
public void testUpdateUser() {
//edit your mock user
User yourUserData = null;
Mockito.when(repo.findById(Matchers.anyInt())).thenReturn(optionalMock);
PowerMockito.when(optionalMock.get()).thenReturn(yourUserData);
Integer userId = 3;
Optional<User> optionalUser = repo.findById(userId);
User user = optionalUser.get();
user.setFirstName("Paul");
repo.save(user);
User updatedUser = repo.findById(userId).get();
Assertions.assertThat(updatedUser.getFirstName()).isEqualTo("Paul");
}
}
#Mock
#Autowired
private UserRepository repo;
This part of your code can lead to confusion. The principle behind mocking is to create a mock objects that is used by the class you want to test.
Looks like you want to test your repository. To do that you can use test slice with #DataJpaTest.
Then in your case, you don't need to mock the optional.
In case you want to test a Service class or another class that use your UserRepository, you will define a mock and your class will looks like this :
#Mock
private UserRepository repo;
#InjectMocks
private UserService service;
#Test
public void testUpdateUser() {
when(repo.findById(anyInt()).get()).thenReturn(aNewInstanceOfUser());
...
}
For more reading.

Couldn't call JpaRepository in test unit

I want to test a serviceImpl (UpdateServiceImpl), the test is like:
#SpringBootTest
public class UpdateUserServiceImplTests {
#Spy
UserRepository userRepository;
#Mock
private UserInfoUpdate userInfoUpdate;
#Autowired
UserRepository userRepository1;
#Spy
#InjectMocks
private UpdateUserServiceImpl updateUserServiceImpl;
#BeforeEach
public void setupMock() {
MockitoAnnotations.openMocks(this);
userInfoUpdate = new UserInfoUpdate();
}
#Test
public void makeUserInfoRight (){
try {
System.out.println(userInfoUpdate.getUsername());
User user = updateUserServiceImpl.makeUserInfoFull(userInfoUpdate);
User user_to_update = userRepository1.findById(userInfoUpdate.getUsername())
.orElseThrow(()
-> new UserNotFoundException("User not found by thisusername : " + "{" +
userInfoUpdate.getUsername() + "}"));
}
And the Method ( makeUserInfoFull() ) in updateUserServiceImpl to be tested is like:
#Service
public class UpdateUserServiceImpl implements UpdateUserService {
#Autowired
UserRepository userRepository;
#Autowired
InfoGuess infoGuess;
#Override
public User makeUserInfoFull(UserInfoUpdate userInfoUpdate)
throws UserNotFoundException {
User user_to_update =
userRepository.findById(userInfoUpdate.getUsername())
.orElseThrow(() -> new UserNotFoundException("User not found by this username : " + "{" + userInfoUpdate.getUsername() + "}"));
The repository is simple one:
#Service
public interface UserRepository extends JpaRepository<User, String> {
}
The JpaRepository interface works fine through #Autowired in test unit (generated userRepository1), but the interface in Method ( makeUserInfoFull() ) I tried #Spy to mock into the test, the Repository (for example: userRepository.findAll() ) keeps return null result.
Is that no way to use real data in the test through JpaRepositoy?
You have to mock that repository also as follow.
#Mock
UserRepository userRepository1;
Then it will be injected within that service class.
Then you can use mockito when call and return what you want.
Mockito.when(userRepository1.findById(anyString()).thenReturn(object)
I think it's not good to use real data on unit test, because:
The database will dirty of test data (contains positive and negative case)
It doesn't make any sense if whenever we have to update our code to avoid that the data is empty or already exist.
Relate with no.2, the unit test should be independent. Not depend on other test even database.
In my opinion, we have two options:
Use Mockito to mock another service such as database or even 3rd party integration. To make our unit test more independent and also faster than using #SpringBootTest
If we are really have to use database integration, you can use H2 Database for your unit test only. So in the real environment you are using Mysql or anything, and using H2 Database only for Unit Test

Spring Boot service layer unit testing with Mockito - mocked data nulled

I'm trying to test service layer using Mockito with JUnit but each time when I run it return object gets nulled.
More info:
UserRepository is a plain spring data repository extending CRUDRepository.
User is a plain JPA entity.
and test:
#RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {
#Mock
private UserRepository userRepository;
#InjectMocks
private UserService userService = new UserService();
private User user;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
#Test
public void tesGetUserIdStatus() {
// given
user = new User();
user.setUserName(userName);
user.setUserId(userId);
User newUser = new User();
// when
Mockito.when(userRepository.findByUserId(userId)).thenReturn(newUser);
User result = userService.getUserById(user);
// then
Assert.assertEquals(newUser, user);
}
}
That test will end up that expected object values get nulled when actual are correct.
Service part which I want to test looks like:
#Component
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUserById(User user) throws EntityNotFoundException {
String userId = user.getUserId();
user = userRepository.findByUserId(userId);
return user;
}
...
I see there's a design issue as well in addition to improper user of mockito. The #InjectMocks is not a good choice. It's implementation is flawed, here's what their javadocs say.
Mockito will try to inject mocks only either by constructor
injection, * setter injection, or property injection in order and as
described below. * If any of the following strategy fail, then
Mockito won't report failure; * i.e. you will have
to provide dependencies yourself.
I think it should be deprecated so developers could nicely do constructor injection. Have a look at this approach;
Refactor the UserService so you could inject the UserRepository via UserService's constructor.
For example: https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-spring-beans-and-dependency-injection.html
Then
#RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {
#Mock
private UserRepository userRepository;
private UserService userService;
private User user;
#Before
public void setUp() throws Exception {
userService = new UserService(userRepository);
}
#Test
public void tesGetUserIdStatus() {
//your test code
}
This is good practice and you could test your logic (services, helpers etc) in isolation with right mocks and stubbing.
However, if you want to verify the correct injection of resources by spring ioc container and want to do operations like validation, you should consider proper Spring/Spring boot tests. Have a look at official Spring testing docs -- https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html
By findByUserId(UserId) you mean findByUserId(userId) and by
Assert.assertEquals(newUser, user) you mean Assert.assertEquals(result, user)? And where do the values userName and userId come from?
And please don't use #InjectMocks to inject an UserService
try this instead:
#Autowired
private UserService userService;
or at least
#InjectMock
private UserService userService;
Your test may fail because the Repository inside UserService cannot be injected properly and the returned result will be null.
There are a few issues in your test:
UserService is instantiated by Mockito (#InjectMocks) - do not initialise it with a new UserService instance - that is the reason you are getting NPE
since you are using MockitoJUnitRunner you do not need to explicitly call MockitoAnnotations.initMocks
when(userRepository.findByUserId(UserId)) - what is UserId?
calling Mockito.when() should be a part of the given section
in the then section you should assert the value you have obtained in the when section

unit test a interface implementation with mock in Spring Boot

I'm trying to write a simple unit test for a service in Spring Boot.
The service calls a method on a repository which returns an instance of User.
I'm trying to mock the repository, because I want to test only the service.
So, the code for Repository:
public interface UserRepository extends MongoRepository<User, String> {
User findByEmail(String email);
}
Service interface:
public interface UserService {
#Async
CompletableFuture<User> findByEmail(String email) throws InterruptedException;
}
Service implementation:
#Service
public class UserServiceImpl implements UserService {
private UserRepository userRepository;
// dependency injection
// don't need Autowire here
// https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-spring-beans-and-dependency-injection.html
public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Async
public CompletableFuture<User> findByEmail(String email) throws InterruptedException {
User user = userRepository.findByEmail(email);
return CompletableFuture.completedFuture(user);
}
}
Unit Test:
#RunWith(SpringRunner.class)
#SpringBootTest
public class UserServiceTest {
#InjectMocks
UserService userService;
#Mock
UserRepository mockUserRepository;
#Before
public void setUp() {
MockitoAnnotations.initMock(this);
}
#Test
public void mustReturnUser() throws InterruptedException {
String emailTest = "foo#bar.com";
User fakeUser = new User();
fakeUser.setEmail(emailTest);
when(mockUserRepository.findByEmail(emailTest)).thenReturn(fakeUser);
User user = userService.findByEmail(emailTest).join();
assertThat(user).isEqualTo(fakeUser);
verify(mockUserRepository).findByEmail(emailTest);
}
}
When I run this test, I got a MockitoException:
org.mockito.exceptions.base.MockitoException:
Cannot instantiate #InjectMocks field named 'userService'.
...
Caused by: org.mockito.exceptions.base.MockitoException: the type 'UserService' is an interface.
Instead of using the interface, I tried to use the real implementation; changing the test like this:
#InjectMocks
UserServiceImpl userService;
Now, the test passes with success, but this don't appear be right (at least for me).
I like to test the UserService that Spring Boot is using (suppose that in a new version of my system, I implement a new UserServicePostgreSQLImpl - now I'm using MongoDB).
(edit: see the bottom edit in the question)
I changed the Unit Test as follows:
#Autowired
#InjectMocks
UserService userService;
but now I got a test failure:
Expected :model.User#383caf89
Actual :null
For some reason, when I use #Autowired, the UserRepository mock doesn't work.
If I change the emailTest to use a real email in my database,
the test passes.
When I use #Autowired,
the test is using the real UserRepository and not a Mock version of UserRepository.
Any help?
Edit: looking at the answers from #msfoster and #dunni, and thinking better, I believe that the correct way is to test every implementation (in my example, use UserServiceImpl userService).
In order for your UserServiceImpl to be autowired when annotating it with #InjectMocks then it needs to registered as a Spring bean itself. You can do this most simply by annotating your UserServiceImpl class with #Service.
This will ensure it is picked up by the component scan in your Spring boot configuration. (As long as the scan includes the package your service class is in!)
You are running your tests with SpringRunner but for mocks you don't really need spring context. Try following code
// Using mockito runner
#RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {
#Mock
UserRepository mockUserRepository;
// Mockito will auto inject mockUserRepository mock to userService via constructor injection
#InjectMocks
UserService userService;
#Test
public void mustReturnUser() throws InterruptedException {
String emailTest = "foo#bar.com";
User fakeUser = new User();
fakeUser.setEmail(emailTest);
when(mockUserRepository.findByEmail(emailTest)).thenReturn(fakeUser);
User user = userService.findByEmail(emailTest).join();
assertThat(user).isEqualTo(fakeUser);
verify(mockUserRepository).findByEmail(emailTest);
}
}
This is just a variation on the #Yogesh Badke answer.
Although you are using spring at runtime,
there is no need to use spring during the unit test.
Instead,
you can mock all the dependencies and set them to the mocks during test setup
(using reflection or setters, if you have them).
Here is some example code:
import org.springframework.test.util.ReflectionTestUtils;
public class TestUserService
{
private static final String VALUE_EMAIL = "test email value";
private UserService classToTest;
#Mock
private User mockUser;
#Mock
private UserRepository mockUserRepository;
#Before
public void beforeTest()
{
MockitoAnnotations.initMock(this);
classToTest = new UserService();
doReturn(mockUser).when(mockUserRepository).findByEmail(VALUE_EMAIL);
ReflectionTestUtils.setField(
classToTest,
"userRepository",
mockUserRepository);
}
#Test
public void findByEmail_goodEmailInput_returnsCorrectUser()
{
final User actualResult;
actualResult = classToTest.findByEmail(VALUE_EMAIL);
assertSame(
mockUser,
actualResult);
}
}
If interface is implemented by more than one class, then use the qualifier name (example below) in Junit beans.xml file to run the respective Junit test case.
Example:
#Autowired
#Qualifier("animal")
private Animal animals;
In Junit beans.xml
<bean id="animal" class="com.example.abc.Lion"/>
where Lion is the implementation class for the Interface Animal.
You need to #InjectMocks for the implementation class. Not the interface class.
Example:
#RunWith(SpringRunner.class)
#SpringBootTest
public class UserServiceTest {
#Mock
UserRepository mockUserRepository;
#InjectMocks
UserServiceImpl userServiceImpl; ------> This is important
}

Categories