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
Related
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;
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.
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
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
}
I am using spring caching layer in my application and I have got an issue as part of writing unit tests to test spring cache layer using Mockito.
Please refer the below code for my problem:
Service layer:
public CustomerServiceImpl implements CustomerService {
#Autowired
private CacheManager cacheManager;
#Autowired
private CustomerRepository customerRepository;//Repository is simple JPA repository interface which contains findByCustomerName()
#Override
#CachePut(value = "#customer", key = "#customer.customerName")
public Customer insertOrUpdate(Customer customer) {
return customerRepository.save(customer);
}
#Cacheable(value="customersCache", key = "#customerName")
public Customer findByCustomerName(String customerName) {
Customer customer = customerRepository.findByCustomerName(customerName);
return customer;
}
}
JUnit test Code for Service layer:
#RunWith(PowerMockRunner.class)
#PrepareForTest(CustomerServiceImplTest.class)
public class CustomerServiceImplTest {
#Spy
CacheManager cacheManager = new ConcurrentMapCacheManager("customersCache");
#Mock
CustomerRepository CustomerRepository;
#InjectMocks
CustomerServiceImpl customerServiceImpl = new CustomerServiceImpl();
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void testCacheForFindByCustomerName() {
Customer customer1 = new Customer();
customer1.setId("1");
customer1.setName("John");
Customer customer2 = new Customer();
customer2.setId("2");
customer2.setName("Sara");
//this should save to cache
Mockito.when(CustomerRepository.save(customer1))
.thenReturn(customer1);
customerServiceImpl.insertOrUpdate(customer1);
//Now it should retreive from cache, but not able to
Mockito.when(CustomerRepository.findByCustomerName(Mockito.any(String.class)))
.thenReturn(customer1, customer2);
Customer result = customerServiceImpl.findByCustomerName("John");
assertThat(result, is(customer1));
result = customerServiceImpl.findByCustomerName("John");
assertThat(result, is(customer1));
}
}
Exception:
I am getting an "java.lang.AssertionError:" because the caching layer did not work and the call has been passed to repository object (twice) which has returned the 'customer2' mock object above i.e., the repository method has been invoked twice for the same key by passing the service layer.
Also, please note that I am using "Mockito" framework for my tests.
I have tried to google for unit tests on spring caching and also referred the below URL, which is almost using the same concept, but it does not work for my above code.
How to test Spring's declarative caching support on Spring Data repositories?
Could you please help to resolve the above exception ?
The Spring Cache Manager relies on Spring managing the application. You can't get that with the PowerMockRunner, you need to use SpringJUnit4Runner. You can still use PowerMock or Mockito programmatically, but not as a Runner.
Typically, you'll turn your unit test into a Spring-style integration test, something like this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class SpringTest {
#Configuration
#EnableCaching
static class SpringConfig{
#Bean
public CustomerService customerService(){
return new CustomerServiceImpl(customerRepository());
}
#Bean
public CustomerRepository customerRepository(){
return Mockito.mock(CustomerRepository.class);
}
}
#Autowired
CustomerService customerService; // this will contain a proper managed cache
#Autowired
CustomerRepository customerRepository; // this is a mockito mock you can fine-tune
}