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

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

Related

Why i can't save anything in my mocked repository

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;

Spring Boot - Unable to mock using Mockito's given(), Optional always empty

I have this Controller class that I want to test:
public class AuthController implements AuthApi {
private final UserService service;
private final PasswordEncoder encoder;
#Autowired
public AuthController(UserService service, PasswordEncoder encoder) {
this.service = service;
this.encoder = encoder;
}
#Override
public ResponseEntity<SignedInUser> register(#Valid NewUserDto newUser) {
Optional<SignedInUser> createdUser = service.createUser(newUser);
LoggerFactory.getLogger(AuthController.class).info(String.valueOf(createdUser.isPresent()));
if (createdUser.isPresent()) {
return ResponseEntity.status(HttpStatus.CREATED).body(createdUser.get());
}
throw new InsufficientAuthentication("Insufficient info");
}
This is my unit test:
#ExtendWith(MockitoExtension.class)
#JsonTest
public class AuthControllerTest {
#InjectMocks
private AuthController controller;
private MockMvc mockMvc;
#Mock
private UserService service;
#Mock
private PasswordEncoder encoder;
private static SignedInUser testSignedInUser;
private JacksonTester<SignedInUser> signedInTester;
private JacksonTester<NewUserDto> dtoTester;
#BeforeEach
public void setup() {
ObjectMapper mapper = new AppConfig().objectMapper();
JacksonTester.initFields(this, mapper);
MappingJackson2HttpMessageConverter mappingConverter = new MappingJackson2HttpMessageConverter();
mappingConverter.setObjectMapper(mapper);
mockMvc = MockMvcBuilders.standaloneSetup(controller)
.setControllerAdvice(new RestApiErrorHandler())
.setMessageConverters(mappingConverter)
.build();
initializeTestVariables();
}
private void initializeTestVariables() {
testSignedInUser = new SignedInUser();
testSignedInUser.setId(1L);
testSignedInUser.setRefreshToken("RefreshToken");
testSignedInUser.setAccessToken("AccessToken");
}
#Test
public void testRegister() throws Exception {
NewUserDto dto = new NewUserDto();
dto.setEmail("ttn.nguyen42#gmail.com");
dto.setPassword("ThisIsAPassword");
dto.setName("ThisIsAName");
// Given
given(service.createUser(dto)).willReturn(Optional.of(testSignedInUser));
// When
MockHttpServletResponse res = mockMvc.perform(post("/api/v1/auth/register")
.contentType(MediaType.APPLICATION_JSON)
.content(dtoTester.write(dto).getJson()).characterEncoding("utf-8").accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultHandlers.print())
.andReturn()
.getResponse();
// Then
assertThat(res.getStatus()).isEqualTo(HttpStatus.CREATED.value());
assertThat(res.getContentAsString()).isEqualTo(signedInTester.write(testSignedInUser).getJson());
}
}
Problem:
The test failed as I got the "Insufficient info" message, because isPresent() is never true, even with given(service.createUser(dto)).willReturn(Optional.of(testSignedInUser)) already there.
When I try to log service.createUser(dto) inside the test method, its isPresent() is always true.
When I try to log inside the controller method, it is always false.
What I have tried:
I suspect that it is because somehow my mockMvc is wrongly configured so I tried to add #AutoConfigureMockMvc but it ended up telling me that "There is no bean configured for 'mockMvc'". I tried to change the UserService mock to the implementation class of but no use.
Please help, I'm really new into Spring's unit tests. Thank you!
The problem is as you have found the Mockito mocking:
given(service.createUser(dto)).willReturn(Optional.of(testSignedInUser))
Specifically, you instruct the mocked service to return an Optional.of(testSignedInUser) if it receives a parameter that is equal to dto. However, depending on the implementation of the equals() method of NewUserDto, this may never occur. For example it returns true only if the same instance is referred to instead of comparing the values of the member variables. Consequently, when passing a dto through the mockMvc it is first serialized and then serialized again by the object mapper, so even though its member variables have the same values, the objects are not considered equal unless you also override the equals() method.
As an alternative, you can relax the mocking to return the Optional.of(testSignedInUser) if any() argument is passed:
given(service.createUser(any())).willReturn(Optional.of(testSignedInUser))
or if the argument isA() specific class:
given(service.createUser(isA(NewUserDto.class))).willReturn(Optional.of(testSignedInUser))
but generally, it is preferred from a testing perspective to be explicit to avoid false positives so for this reason I advise to double check and and / or override the NewUserDto#equals() method if possible.
Thanks to #matsev answer, I was able to solve half of the problem, but there was this thing as well:
#Controller
public class AuthController implements AuthApi {
private final UserService service;
private final PasswordEncoder encoder;
// ...
}
I left the service fields as final, which did not allow Mockito to inject or mutate the dependency. After removing final, I got the test to work now.
EDIT: Please refer to this thread for workaround Mockito, #InjectMocks strange behaviour with final fields
EDIT 2: By design, Mockito does not inject mocks to final fields: https://github.com/mockito/mockito/issues/352
Doing so violates other APIs and can cause issues. One way to fix this is to just use constructor injection, remove #InjectMocks, then you can just use final fields.
// No InjectMocks
private AuthController controller;
#Mock
private UserService service;
#Mock
private PasswordEncoder encoder;
#BeforeEach
public void setup() {
controller = new AuthController(service, encoder);
// ...
}

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.

Testing in Spring project - injecting repository into service with Mockito

I'm trying to understand how to use Mockito in a Spring project, but I'm a bit stuck with the following:
I'm about to test a service with a real (in-memory) repository. I'm mocking every other object that's being used by that service. If I understand correctly, annotating an object with #Mock will be mocked, #Spy will use a real object, and with #InjectMocks will kind of "autowire" these annotated objects. However, I think I misunderstood #Spy because the following code is not working (seems like a mock object is inserted, neither a DB interaction, neither a NullPointerException comes up):
#SpringBootTest
#RunWith(SpringRunner.class)
public class UserServiceTests {
#Spy
#Autowired
private UserRepository userRepository;
#Mock
private MessageService messageService;
#InjectMocks
private UserService userService = new UserServiceImpl();
#Test
public void testUserRegistration() {
userService.registerUser("test#test.test");
Assert.assertEquals("There is one user in the repository after the first registration.", 1, userRepository.count());
}
}
If I remove the #Spy annotation and insert the repository in the service with reflection, it works:
ReflectionTestUtils.setField(userService, "userRepository", userRepository);
This seems inconvenient, and I'm pretty sure that #Spy is meant to do this. Can anyone point out what I'm missing/misunderstanding?
Edit: one more thing - with the #Spy annotation I'm not getting a NullPointerException on save, but it seems like a mock object is inserted instead of the real repository.
Edit 2: I think this is the answer for my confusion:
SpringBoot Junit bean autowire
The problem is that you probably #Autowired some services/repositories in UserService like these:
public class UserServiceImpl() {
#Autowired
private UserRepository userRepository;
#Autowired
private MessageService messageService;
// ... all the stuff
}
Mockito tries to inject mocks through constructor or setters which are not available. Try to redesign your class UserServiceImpl, so it has constructor like:
public class UserServiceImpl() {
private final UserRepository userRepository;
private final MessageService messageService;
#Autowired
public UserServiceImpl(UserRepository userRepository, MessageService messageService) {
this.userRepository = userRepository;
this.messageService = messageService;
}
Then you can instantiate the service in #Before with the constructor using those mocks.

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