Spring AOP Aspect not working using Mockito - spring

I have an #Aspect that weaves the execution of all my controller action methods. It works fine when I run the system, but not in unit testing. I'm using Mockito and junit in the following way:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("file:**/spring-context.xml")
#WebAppConfiguration
public class UserControllerTest {
private MockMvc mockMvc;
#Mock
private RoleService roleService;
#InjectMocks
private UserController userController;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
...
mockMvc = MockMvcBuilders.standaloneSetup(userController).build();
}
...
}
with some #Test using mockMvc.perform().
And my Aspect are:
#Pointcut("within(#org.springframework.stereotype.Controller *)")
public void controller() { }
#Pointcut("execution(* mypackage.controller.*Controller.*(..))")
public void methodPointcut() { }
#Around("controller() && methodPointcut()")
...

First it is necessary to use webAppContextSetup as Jason suggested:
#Autowired
private WebApplicationContext webApplicationContext;
#Before
public void setUp() throws Exception {
...
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
At this point the aspect should be triggered but Mockito will not inject mocks. This is because Spring AOP uses a proxy object and the mocks are being injected to this proxy object instead of the proxied object. To fix this it is necessary to unwrap the object and use ReflectionUtils instead of #InjectMocks annotation:
private MockMvc mockMvc;
#Mock
private RoleService roleService;
private UserController userController;
#Autowired
private WebApplicationContext webApplicationContext;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
UserController unwrappedController = (UserController) unwrapProxy(userController);
ReflectionTestUtils.setField(unwrappedController, "roleService", roleService);
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
...
public static final Object unwrapProxy(Object bean) throws Exception {
/*
* If the given object is a proxy, set the return value as the object
* being proxied, otherwise return the given object.
*/
if (AopUtils.isAopProxy(bean) && bean instanceof Advised) {
Advised advised = (Advised) bean;
bean = advised.getTargetSource().getTarget();
}
return bean;
}
At this point any call to when(...).thenReturn(...) should work properly.
It is explained here: http://kim.saabye-pedersen.org/2012/12/mockito-and-spring-proxies.html

You are probably using Spring AOP, in which case the bean has to be a Spring bean for AOP to work, by not autowiring in the controller it is bypassing the Spring AOP mechanism totally.
I think the fix should be to simply inject in the controller
#Autowired
#InjectMocks
private UserController userController;

Related

Spring-Boot UnitTest: #Value in ConstraintValidator

I'm currently providing coverage - testing the validation of my DTO through MockMVC request call.
I recently introduced a new field in my Registration ConstraintValidator, supportedSpecializations, of which I inject the values from application.properties for easy maintenance and expandability. see code fragment below:
#Component
public class RegistrationValidator implements ConstraintValidator<Registration, String> {
//campus.students.supportedspecializations="J2E,.NET,OracleDB,MySQL,Angular"
#Value("${campus.students.supportedspecializations}")
private String supportedSpecializations;
private String specializationExceptionMessage;
//All ExceptionMessages are maintained in a separate class
#Override
public void initialize(Registration constraintAnnotation) {
exceptionMessage = constraintAnnotation.regionException().getMessage();
}
#Override
public boolean isValid(RegistrationData regData, ConstraintValidatorContext context) {
String[] specializations = supportedSpecializations.split(",");
boolean isValidSpecialization = Arrays.stream(specializations)
.anyMatch(spec -> spec.equalsIgnoreCase(regData.getSpec()));
if (!isValidSpecialization){
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(specializationExceptionMessage)
.addConstraintViolation();
return false;
}
//additional validation logic...
return true;
}
}
Unit tests now fail due to the field not being injected by the defined property of the #Value annotation.
I'm not sure if ReflectionTestUtils could help in my case, so any suggestions are greatly appreciated about how to inject the required values in UnitTests.
Spring version is 2.1.0
I'm currently testing with the following snippet:
#InjectMocks
private StudentController mockRestController;
#Mock
private StudentService mockStudentService;
#Mock
private ValidationExceptionTranslator mockExceptionTranslator;
#Value("${campus.students.supportedspecializations}")
private String supportedSpecializations;
private MockMvc mockMvc;
private static final String VALIDATION_SUCCESSFUL = "success";
private static final String VALIDATION_FAILED = "failed";
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(mockRestController).build();
doReturn(
ResponseEntity.status(HttpStatus.OK)
.header("Content-Type", "text/html; charset=utf-8")
.body(VALIDATION_SUCCESSFUL))
.when(mockStudentService).insertStudent(Mockito.any());
doReturn(
ResponseEntity.status(HttpStatus.BAD_REQUEST)
.header("Content-Type", "application/json")
.body(VALIDATION_FAILED))
.when(mockExceptionTranslator).translate(Mockito.any());
}
#Test
public void testValidation_UnsupportedSpecialization() throws Exception {
MvcResult mvcResult = mockMvc.perform(
post("/Students").contentType(MediaType.APPLICATION_JSON_UTF8).content(
"{\"registrationData\":{\"spec\":\"unsupported\"}}"))
.andExpect(status().isBadRequest())
.andReturn();
assertEquals(VALIDATION_FAILED, mvcResult.getResponse().getContentAsString());
verify(mockExceptionTranslator, times(1)).translate(Mockito.any());
verify(mockStudentService, times(0)).insertStudent(Mockito.any());
}
I tried annotating my Test class with #RunWith(SpringRunner.class) and #SpringBootTest(classes = Application.class), but the validation test still fails due to #Value not being resolved. I might be wrong but I think the ConstraintValidator's instance is created before we reach the restController, so a MockMVC perform(...) call couldn't simply just make sure the appropriate #Value in the validator gets injected into supportedSpecializations.
Solved the issue the following way:
Added the following annotations to the Test Class
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class)
#AutoConfigureMockMvc
Then autowired the controller and mockMVC, finally annotated service and translator with Spring's #MockBean
So currently it looks something like this:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class)
#AutoConfigureMockMvc
public class StudentValidatorTest {
#Autowired
private StudentController mockRestController;
#MockBean
private StudentService mockStudentService;
#MockBean
private ValidationExceptionTranslator mockExceptionTranslator;
#Autowired
private MockMvc mockMvc;
private static final String VALIDATION_SUCCESSFUL = "success";
private static final String VALIDATION_FAILED = "failed";
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(mockRestController).build();
doReturn(
ResponseEntity.status(HttpStatus.OK)
.header("Content-Type", "text/html; charset=utf-8")
.body(VALIDATION_SUCCESSFUL))
.when(mockStudentService).insertStudent(Mockito.any());
doReturn(
ResponseEntity.status(HttpStatus.BAD_REQUEST)
.header("Content-Type", "application/json")
.body(VALIDATION_FAILED))
.when(mockExceptionTranslator).translate(Mockito.any());
}
//...and tests...
Yes,
use ReflectionTestUtil.
Use ReflectionTestUtil.setField to set the value of supportedSpecializationsin the
setup() method (junit < 1.4)
or in the #Before annotated method (junit > 1.4) in your unit test.
More Details
I recommend against using MockMVC for your unit tests;
it is fine for integration tests,
just not unit tests.
There is no need to start Spring for a Unit Test;
you never need Spring to perform injections for your unit tests.
Instead,
instantiate the class you are testing and call the methods directly.
Here is a simple example:
public class TestRegistrationValidator
{
private static final String VALUE_EXCEPTION_MESSAGE = "VALUE_EXCEPTION_MESSAGE";
private static final String VALUE_SUPPORTED_SPECIALIZATIONS = "BLAMMY,KAPOW";
private RegistrationValidator classToTest;
#Mock
private Registration mockRegistration;
#Mock
private RegionExceptionType mockRegionExceptionType; // use the actual type of regionExcpeption.
#Before
public void preTestSetup()
{
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(classToTest, "supportedSpecializations", VALUE_SUPPORTED_SPECIALIZATIONS);
doReturn(VALUE_EXCEPTION_MESSAGE).when(mockRegionExceptionType).getMessage();
doReturn(mockRegionExceptionType).when(mockRegion).regionException();
}
#Test
public void initialize_allGood_success()
{
classToTest.initialize(mockRegistration);
...assert some stuff.
...perhaps verify some stuff.
}
}
The best option i think for you is to use constructor injection in your RegistrationValidator.class , so that you can assign mock or test values directly for testing as well when required. Example :
#Component
class ExampleClass {
final String text
// Use #Autowired to get #Value to work.
#Autowired
ExampleClass(
// Refer to configuration property
// app.message.text to set value for
// constructor argument message.
#Value('${app.message.text}') final String text) {
this.text = text
}
}
This way you can set your mock values to the variables for unit testing.
Yes, you are right a custom constructor is not an option here, then you could introduce a configuration class where you have these values read from yml or property and autowire that in the validator .That should work for you.
Or
You can provide the #Value properties in a separate test.yml or test.properties and specify that to be taken up while running your integrated tests. In that case you should be able to resolve these values.
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = ExampleApplication.class)
#TestPropertySource(locations="classpath:test.properties")
public class ExampleApplicationTests {
}
The #TestPropertySource annotation has higher precedence order and it should resolve your values.

How to inject MockMvc with multiple objects in Spring Boot REST Junit test case

I have a SpringBoot REST API connecting to Oracle DB.
My controller calls BusinessImpl layer and in-turn BusinessImpl calls multiple DAO layers (Controller -> BusinessImpl -> DAO1, DAO2, DAO3)
The below test case works perfectly
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration()
#TestPropertySource("classpath:dev-manifest.yml")
#ContextConfiguration(classes = Application.class)
#ConfigurationProperties(prefix = "env")
#SpringBootTest
public class MyTest
{
private static final String REQUEST_URI = "/v1/registration/accounts/links";
private MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#Before
public void setup()
{
this.mockMvc = webAppContextSetup(webApplicationContext).build();
}
#Test
public void testSave()
{
String testInput = "some json input";
mockMvc.perform(post(REQUEST_URI).content(testInput).contentType(contentType))
.andExpect(status().isOk());
}
But I do not want to hit real database during junit test case. So I wrote a mock.
Working Code
#Mock
private SecurityAuditDAO securityAuditDAO;
#InjectMocks
private RegistrationBusinessImpl registrationBusinessImpl;
#Test
public void testSave()
{
when(securityAuditDAO.getState(Mockito.any())).thenReturn("somestring");
SomeRequest someRequest = new SomeRequest();
someRequest.setStatus("SUCCESS");
SomeResponse status = registrationBusinessImpl.createUser(SomeRequest, "127.0.0.1");
}
The above code worked perfectly. In businessImpl class securityAuditDAO.getState returned "somestring". But when I introduced mockMvc.perform it stopped working.
Not Working
#Test
public void testSave()
{
when(securityAuditDAO.getState(Mockito.any())).thenReturn("somestring");
String testInput = "some json input";
mockMvc.perform(post(REQUEST_URI).content(testInput).contentType(contentType))
.andExpect(status().isOk());
}
The above code was still hitting the database. So I realized that I should inject mockMvc with securityAuditDAO so I added the following line
this.mockMvc = MockMvcBuilders.standaloneSetup(securityAuditDAO).build();
Code
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#Mock
private SecurityAuditDAO securityAuditDAO;
#InjectMocks
private RegistrationBusinessImpl registrationBusinessImpl;
#InjectMocks
RegistrationApiController registrationApiController;
#Before
public void setup()
{
MockitoAnnotations.initMocks(this);
//this.mockMvc = webAppContextSetup(webApplicationContext).build();
this.mockMvc = MockMvcBuilders.standaloneSetup(securityAuditDAO).build();
//this.mockMvc = MockMvcBuilders.standaloneSetup(registrationApiController).build();
//ReflectionTestUtils.setField(mockMvc, "securityAuditDAO", securityAuditDAO);
}
I tried injecting securityAuditDAO. But if I do that, my other autowired instances in BusinessImpl were null.
How to inject securityAuditDAO without affecting others or how to inject both webApplicationContext and securityAuditDAO.
Also tried ReflectionTestUtils.setField but it didn't work as expected.

How can I test my SpringBoot RestController using a MockMvc when I rely on a Spring Validator?

In my rest-controller I am validating the input JSON with a custom Spring validator class.
When I now want to write unit test for the controller then I am getting the error that my Validator could not be found...
I am using constructor injecting for the two components in my rest-controller.
#Autowired
public JobController(JobValidator jobValidator, JobService jobService) {
this.jobValidator = jobValidator;
this.jobService = jobService;
}
And here my corresponding Test class.
#RunWith(SpringRunner.class)
#WebMvcTest(JobsController.class)
#AutoConfigureMockMvc
public class MailMonitorJobControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private JobService jobService;
#Test
public void givenJobs_whenGetJobs_thenReturnJsonArray() throws Exception {
Job job = new Job("TEST");
List<Job> allJobs = Arrays.asList(job);
Mockito.when(jobService.getAllJobs()).thenReturn(allJobs);
mockMvc.perform(get("/api/v1/test")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}
Appreciate any help, hints or suggestions!
So thanks to #pvpkiran! I had to add the JobValidator also as a Mock!
#RunWith(SpringRunner.class)
#WebMvcTest(JobsController.class)
#AutoConfigureMockMvc
public class MailMonitorJobControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private JobService jobService;
#MockBean
private JobValidator jobValidator;
#Test
public void givenJobs_whenGetJobs_thenReturnJsonArray() throws Exception {
Job job = new Job("TEST");
List<Job> allJobs = Arrays.asList(job);
Mockito.when(jobService.getAllJobs()).thenReturn(allJobs);
mockMvc.perform(get("/api/v1/test")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}

Mocking in Spring Boot

I've generated a Spring Boot web application using Spring Initializer, embedded Tomcat, Thymeleaf template engine, and package as an executable JAR file.
Technologies used:
Spring Boot 1.4.2.RELEASE, Spring 4.3.4.RELEASE, Thymeleaf 2.1.5.RELEASE, Tomcat Embed 8.5.6, Maven 3, Java 8
I have these classes:
package com.tdk.helper;
#Component
public class BookMessageDecoder implements MessageDecoder {
private String messageData;
public BookMessageDecoder() {
super();
}
/**
* #param data4
*/
public BookMessageDecoder(String messageData) {
this.messageData=messageData;
}
..
}
#RestController
public class BookCallBackController {
BookSystemManager bookSystemManager;
#Autowired
BookMessageDecoder messageDecoder;
#Autowired
public BookCallBackController(BookSystemManager bookSystemManager) {
this.bookSystemManager = bookSystemManager;
}
..
}
#RunWith(SpringRunner.class)
public class BookCallBackControllerTests {
#MockBean
BookMessageDecoder messageDecoder;
private BookCallBackController controller;
#Before
public void setUp() throws Exception {
given(this.messageDecoder.hasAlarm()).willReturn(false);
controller = new BookCallBackController(new StubBookSystemManager());
}
..
}
Even I am mocking the bean bookMessageDecoder, is null when I use it !
For Controller test you can always use springs #WebMvcTest(BookCallBackController.class) annotations.
Also you need to configure a mockMvc for mock Http request to your controller.
After that you can autowire mockMvc #Autowired MockMvc mockMvc;
Now you can mock you dependency to controller #MockBean
BookMessageDecoder messageDecoder;
#RunWith(SpringRunner.class)
#WebMvcTest(BookCallBackController.class)
#AutoConfigureMockMvc
public class BookCallBackControllerTests {
#MockBean
BookMessageDecoder messageDecoder;
#Autowired
MockMvc mockMvc;
#Before
public void setUp() throws Exception {
given(this.messageDecoder.hasAlarm()).willReturn(false);
}
..
}

SpringMVC Unit testing - Service method is not called

When I run unit test on the controller by mocking the service bean, it looks like the service method is not called at all. Is this expected behvaior or am I missing something?
SearchController.java
#Controller
public class SearchController {
#Autowired
SearchService searchService;
#RequestMapping(value="/search", method=RequestMethod.GET)
public String showSearchPage(Model model){
model.addAttribute("list", searchService.findAll());
return "search";
}
}
SearchControllerTest.java
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration("file:src/main/webapp/WEB-INF/springapp-servlet.xml")
public class SearchControllerTest {
#Autowired
private WebApplicationContext webAppContext;
private MockMvc mockMvc;
private SearchService searchServiceMock;
#Before
public void setUp(){
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webAppContext).build();
this.searchServiceMock = Mockito.mock(SearchServiceImpl.class);
}
#Test
public void testShowSearchPage() throws Exception{
when(searchServiceMock.findAll())
.thenReturn(Arrays.asList("abc", "acb", "123"));
this.mockMvc.perform(get("/search.do"))
.andExpect(status().isOk())
.andExpect(view().name("search"))
.andExpect(forwardedUrl("/WEB-INF/jsp/search.jsp"))
.andExpect(model().attribute("list", hasSize(3)));
verify(searchServiceMock, times(1)).findAll(); //this test is failing
verifyNoMoreInteractions(searchServiceMock);
}
}
When I run the test, it seems like findAll() method isn't getting called and it is throwing exception. "Wanted but not invoked searchServiceImpl.findAll()"
What mistake am I making here?
-------------Update------------------
SearchControllerTestNew.java
public class SearchControllerTestNew {
#InjectMocks
SearchController searchController;
#Mock
SearchService searchServiceMock;
#Mock
View mockView;
MockMvc mockMvc;
#Before
public void setUp(){
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(searchController).setSingleView(mockView)
.build();
}
#Test
public void testShowSearchPage() throws Exception{
when(searchServiceMock.findAll())
.thenReturn(Arrays.asList("abc", "acb", "123"));
this.mockMvc.perform(get("/search.do"))
.andExpect(status().isOk())
.andExpect(view().name("search"))
.andExpect(model().attribute("list", hasSize(3)))
.andExpect(forwardedUrl("/WEB-INF/jsp/search.jsp"));//this fails now
verify(searchServiceMock, times(1)).findAll();
verifyNoMoreInteractions(searchServiceMock);
}
}
Your SearchController will use the autowired SearchService (part of your application context) not your mock, notice you create a mock but you are not using it anywhere, instead you create a MockMvc based on your application context.
A solution would be, using a standalone setup which gives you full control over the instantiation and initialization of controllers and their dependencies:
#Before
public void setUp(){
this.searchServiceMock = Mockito.mock(SearchServiceImpl.class);
mockMvc = MockMvcBuilders.standaloneSetup(new SearchController(seachServiceMock))
.setViewResolvers(viewResolver())
.build();
}
private ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/jsp/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
I've omitted some MVC infrastructure configuration in this example check the MockMvcBuilder documentation for further configuration.
Although you have mocked the SearchService it isn't injected to the controller (at least not evident from code you shared).
Thus the verify(searchServiceMock, times(1)).findAll(); is bound to fail as it is indeed never invoked.
Take a look at #InjectMock how mocks are injected.
Also you need to add below LOCs for whole thing to work
First introduce a field
#InjectMocks
private SearchController controller;
Second in setUp() add
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
You can use #MockBean for your mocks, then they will be injected to Context automatically.
Doc: https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-testing-spring-boot-applications-mocking-beans

Resources