Reflection not working for Spring autowired parameters - java

I'm trying to generate an integration test without using #DataJpaTest in order to understand better the concepts.
I've noticed that with Reflection I'm not able to get or set The Dao within my Service layer under test. So when it comes to access the Dao got with Reflection API it returns a NullPointerException. I've tested both Java Reflection API and ReflectionTestUtils of Spring Framework. Here is the snippet of code
UserService userService;
#Before
public void setUp(){
userService = new UserServiceImpl();
UserDao userDao = (UserDao) ReflectionTestUtils.getField(userService, "userDao");
userDao.deleteAll(); //HERE RETURNS A NULLPOINTER
...
}
Consider that in UserServiceImpl.java I inject UserDao (Interface which extends JpaRepository using #Autowired annotation of Spring framework.
How can I access the Dao (implemented by Spring framework) from my Service? Thanks!

One handy trick is to make sure your test Spring configuration is working correctly by verifying your autowired dependencies are actually getting autowired. For example:
#Autowired
UserDao userDao;
#Autowired
UserServiceImpl userService;
#Test
public void verifySpringContext() {
assertNotNull(userDao);
assertNotNull(userService.getUserDao());
}
I suspect that there's an issue with the Spring configuration in your test, preventing userDao from being autowired.

Related

Semi-integration testing using mocks and application context

I need to write an e2e test on REST level, that sends real requests. So I want to use application context instead of mocking beans.
RestController.class has an autowired MyService.class, and this MyService.class is dependent on two repository classes. So I tried to mock repositories and inject them in the real Service in the following way:
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = MyService.class)
#AutoConfigureMockMvc
class MyControllerTest {
#Mock private MyRepository repository;
#Mock private AnotherRepository anotherRepository;
#Autowired #InjectMocks private MyService service;
#InjectMocks private MyController controller;
#RepeatedTest(1)
void someTest() throws Exception {
MockHttpServletResponse response =
mvc.perform(...); assertThat(...);
}
}
#Service
#RequiredArgsConstructor
public class MyService {
private final MyRepository repository;
private final AnotherRepository another; ...}
But I get the following error:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'myRepository'
I also tried to use #ContextConfiguration(classes = {MyConfig.class }) with no success:
#EnableWebMvc
#Configuration
public class MyConfig {
#Autowired private MyService service;
#Mock private MyRepository repository;
#Mock private AnotherRepository another;
}
Is there something I'm missing?
First off you should not use #Mock which is relevant for plain unit testing with mockito, use #MockBean instead. #MockBean "belongs" to the spring universe, it will create a bean (backed by mockito after all) but also will place it into the application context. If there is another bean of this type - it will substitute it.
I also don't think you should use #ExtendWith because #SpringBootTest in the relatively recent spring boot versions already has it defined (check the source code of #SpringBootTest annotation to make sure that's the case indeed.
Now other mistakes are more complicated.
To start with you don't need a spring boot test at all here. It also has a wrong parameter actually (which should point on a configuration class).
You should use #WebMvcTest instead. It seems that you only want to test the controller, and mock the service. #SpringBootTest in general tries to mimick the loading of the whole application, whereas the #WebMvcTest only tests a web layer. BTW, with this approach you don't need to even mock the Repository class because the controller will use the mock of the service, and since you don't load the whole application context there is no need to define a repository at all (the service being a mock doesn't use it anyway)
Add #RunWith(SpringRunner.class) to class MyControllerTest
#RunWith(SpringRunner.class)
class MyControllerTest {
}

Spring Boot Mockito - #InjectMocks - How to mock selected dependencies only

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("john.doe#example.com");
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
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.
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.

Mock JWT Utils to validate Token

I want to create JUnkt test for this endpoint:
#Autowired
private JwtTokenProvider jwtTokenProvider;
#PostMapping("reset_token")
public ResponseEntity<?> resetToken(#Valid #RequestBody ResetPasswordTokenDTO resetPasswordTokenDTO, BindingResult bindResult) {
final String login = jwtTokenProvider.getUsername(resetPasswordTokenDTO.getResetPasswordToken());
}
Full code: Github
JUnit test:
#Test
public void resetTokenTest_NOT_FOUND() throws Exception {
when(usersService.findByResetPasswordToken(anyString())).thenReturn(Optional.empty());
mockMvc.perform(post("/users/reset_token")
.contentType(MediaType.APPLICATION_JSON)
.content(ResetPasswordTokenDTO))
.andExpect(status().isNotFound());
}
I get NPE at this line when I run the code:
final String login = jwtTokenProvider.getUsername(resetPasswordTokenDTO.getResetPasswordToken());
How I can mock jwtTokenProvider properly? As you can see I have a file with test data which I load but the token is not extracted. Do you know how I can fix this issue?
The most straightforward way is to use Mockito and create mock instances and pass it directly to your controller class using constructor injection.
However, if you do not wish to use constructor injection (I recommend you to use it though, as it is much more explicit) you need to define your beans in a separate test configuration class
#Profile("test")
#Configuration
public class TestConfiguration {
#Bean
public JwtTokenProvider mockJwtTokenProvider() {
return Mockito.mock(JwtTokenProvider.class);
}
}
Also, add the correct profile to your test class by #ActiveProfiles("test")
You can consider using a #MockBean directly in your test class to mock your JwtTokenProvider. #MockBean annotation is Spring-ish and is included in spring-boot-starter-test. The Spring Boot documentation summarizes it well:
Spring Boot includes a #MockBean annotation that can be used to define
a Mockito mock for a bean inside your ApplicationContext. You can use
the annotation to add new beans or replace a single existing bean
definition. The annotation can be used directly on test classes, on
fields within your test, or on #Configuration classes and fields. When
used on a field, the instance of the created mock is also injected.
Mock beans are automatically reset after each test method.
The #MockBean annotation will make Spring look for an existing single bean of type JwtTokenProvider in its application context. If it exists, the mock will replace that bean, and if it does not exist, it adds the new mock in the application context.
Your test class would look like this:
import org.springframework.boot.test.mock.mockito.MockBean;
#MockBean
#Qualifier("xxx") //If there is more than one bean of type JwtTokenProvider
private JwtTokenProvider jwtTokenProvider;
#Test
public void resetTokenTest_NOT_FOUND() throws Exception {
when(jwtTokenProvider.getUsername(anyString())).thenReturn(Optional.empty());
mockMvc.perform(post("/users/reset_token")
.contentType(MediaType.APPLICATION_JSON)
.content(ResetPasswordTokenDTO))
.andExpect(status().isNotFound());
}
You might also want to check this and this.

How to run a unit test using the SpringBoot 2

I have the following test:
#SpringBootTest
#ExtendWith(SpringExtension.class)
class BookServiceImplTest {
#MockBean
private BookRepository bookRepository;
#MockBean
private LibraryService libraryService;
#Autowired
private BookServiceImpl bookService;
#Test
void create() {
BookRequestDTO bookRequestDTO = new BookRequestDTO();
Library library = new Library();
Book expectedBook = new Book();
when(libraryService.getById(bookRequestDTO.getLibraryId()))
.thenReturn(library);
when(bookRepository.save(any(Book.class)))
.thenReturn(expectedBook);
Book actualBook = bookService.create(bookRequestDTO);
assertEquals(expectedBook, actualBook);
}
}
It is okay, and it runs, but I was wondering is there a way to run this as a Unit test instead of a Integration tests and still use #MockBean #Autowired. Or am I missing some point?
I tried leaving only #ExtendWith(SpringExtension.class), but I get an Exception about BookServiceImpl bean not found.
I know how to do it using MockitoExtension and #Mock, #InjectMocks, but I was wondering if there is a more SpringBoot way of doing it?
You can make it unit test in four steps:
Remove #SpringBootTest annotation, as it is spinning up the entire Spring context ―not useful for unit tests where all collaborators are mocked
Remove #Autowired annotation from BookServiceImpl declaration and add a #BeforeEach setup method where you initialize bookService passing bookRepository and libraryService as parameters
Use MockitoExtension instead of SpringExtension in the ExtendWith annotation. Here I'm assuming you are able to use a library like Mockito for mocking your collaborators
Use Mockito's #Mock instead of #MockBean, as we are manually initializing bookService so there is no need to deal with Spring beans
To add a bit more on your first question: #Mockbean and #Autowired are annotations that make sense for integration tests as they handle the mocking and injection of beans. Unit tests should consider this class in isolation, mocking interactions with other classes, so there is no need to spin up the application context and set up beans.
Remove #SpringBootTest, this will load whole context and slow down your tests. The role of #MockBean is to create a mock from the specified bean and add it to the context. Since there's no context running, There's not point in using #MockBean
Annotate your unit test class with #RunWith(SpringRunner.class)
For injecting dependencies, this is a good practice to create a configuration file and mock beans explicitly and create the target bean using them. Assuming you're using constructor-based injection:
#Profile("test")
#Configuration
public class BookServiceTestConfiguration {
#Bean
public BookRepository bookRepository() {
return Mockito.mock(BookRepository.class);
}
#Bean
public LibraryService libraryService() {
return Mockito.mock(LibraryService.class);
}
#Bean
public BookService bookService() {
BookServiceImpl bookService = new BookServiceImpl(
bookRepository(), libraryService()
);
return userGroupService;
}
}
Then write your test class as:
#ActiveProfiles("test")
#Import(BookServiceTestConfiguration .class)
#RunWith(SpringRunner.class)
public class BookServiceUnitTest {
#Autowired
private BookService bookService;
// write unit tests
}
for further information, read this article

DataJpaTest with a single autowired tested bean

I would like to create a test, which targets exactly one service class (#Service) in my project.
My service class has two kinds of dependencies:
other services, which I want to mock with a #MockBean
JPA repositories, which I do not want to mock; I want them to be real Spring beans, configured against H2 database
My services only depend on other services or repositories. Repositories have no dependencies - imagine JPA repository interfaces.
I have come up with following solution, which works fine:
#DataJpaTest
class FooServiceTests {
#Autowired
private FooRepository fooRepository;
#MockBean
private BarService barService;
#Test
void testService() {
FooService fooService = new FooService(barService, fooRepository);
Assertions.assertNotNull(barService);
Assertions.assertNotNull(fooRepository);
}
}
My question is, whether there is an alternative to this solution, which does not require manual assembly of the tested bean. A solution, which would have Spring assemble the bean for me, by using mocked services and real (H2) repositories. A solution, which allows tested bean to have dependencies #Autowired into private fields. Something like this: (which obviously does not work):
#DataJpaTest
#ContextConfiguration(classes = FooService.class)
class FooService2Tests {
#MockBean
private BarService barService;
#Autowired
private FooService fooService;
#Test
void testService() {
Assertions.assertNotNull(fooService);
Assertions.assertNotNull(barService);
Assertions.assertNotNull(fooService.getBarService());
Assertions.assertNotNull(fooService.getFooRepository());
}
}
I wanted to avoid using #SpringBootTest. If some answer suggests so, than it should explain, why that is the best way on how to go.

Categories

Resources