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);
}
..
}
Related
I have :
an interface : EntityService
a first implementation : EntityServiceImpl - This class is annotated with #Primary
an other one : EntityServiceClientImpl
and a controller that has this field #Autowired EntityService
I would like to do a test on this controller and for this test to be unitary I mock EntityService.
So of course this code does not work because Spring detects two beans annotated with Primary :
#Configuration
class EntityControllerTestConfig {
#Bean
#Primary
EntityService entityService() {
return mock(EntityService.class);
}
}
#RunWith(SpringRunner.class)
#SpringBootTest(classes = TestApplication.class)
#WebAppConfiguration
#ContextConfiguration(classes = EntityControllerTestConfig.class)
public class EntityControllerTest {
#Autowired
private EntityService entityService;
...
#SpringBootApplication(scanBasePackages= "com.company.app")
#EntityScan (basePackages = {"com.company.app" }, basePackageClasses = {Jsr310JpaConverters.class })
#EnableJpaRepositories("com.company.app")
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
I tried to find an other way to mock and to exclude EntityServiceClient on test configuration but i was not able to mock. (cf : exclude #Component from #ComponentScan )
I finaly found that solution : a spring context (with controller, controllerAdvice and mock of service) and not a spring boot context
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class EntityControllerTest {
#Configuration
public static class EntityControllerTestConfig {
#Bean
public EntityService entityService() {
return mock(EntityService.class);
}
#Bean
public EntityController entityController() {
return new EntityController(entityService());
}
}
#Autowired
private EntityService entityService;
#Autowired
private EntityController entityController;
private MockMvc mockMvc;
#Before
public void setup() throws DomaineException {
this.mockMvc = MockMvcBuilders
.standaloneSetup(entityController)
.setControllerAdvice(new myControllerAdvice())
.build();
NB : Since Spring 4.2 you can set your ControllerAdvice like that.
You can approach it slightly differently and combine #WebMvcTest with #MockBean annotation to test just the controller with it's own minimal context.
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = EntityController.class)
public class EntityControllerTest {
#MockBean
private EntityService entityService;
#Autowired
private MockMvc mvc;
In this example EntityService will be mocked while MockMvc can be used to assert the request mappings in controller.
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());
}
}
I am working with Spring Framework and Spring Security
About Testing
For a set of Test classes for #Controller with security, .apply(springSecurity() and #WithUserDetails(value="something") are used
#Before
public void setUp(){
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.apply(springSecurity())// <---
.build();
}
For other set of Test classes for #Controller without security, therefore .apply(springSecurity()) and #WithUserDetails(value="something") are not used.
#Before
public void setUp(){
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.build();
}
Until here all about for #Controller with and without security work fine.
The problem is for the #Service, when #EnableGlobalMethodSecurity is defined and the #Service methods are annotated with #PreAuthorize("hasRole('ROLE_ADMIN')"), all the other Test classes for #Service where security is not required fail now with:
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException:
An Authentication object was not found in the SecurityContext
Of course it because the #Test methods do not use #WithUserDetails(value="something")
Thus, practically .apply(springSecurity()) does the job, but it for a Web environment through MockMvcBuilders.webAppContextSetup(webApplicationContext)
But for the server side, where security is not needed, I have:
#Transactional
#RunWith(Parameterized.class)
#ContextConfiguration(classes={RootApplicationContext.class})
#ActiveProfiles(resolver=TestActiveProfilesResolver.class)
#TestExecutionListeners(listeners={LoggingTestExecutionListener.class}, mergeMode=MergeMode.MERGE_WITH_DEFAULTS)
public class PersonaServiceImplTest {
private static final Logger logger = LoggerFactory.getLogger(PersonaServiceImplTest.class.getSimpleName());
#ClassRule
public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();
#Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
#Autowired
private Environment environment;
...
Thus MockMvcBuilders.webAppContextSetup(webApplicationContext) has no sense to be used. What is the best way to resolve this?
You can use #WithUserDetails and #WithMockUser to test method security as well.
For the tests to pick up on method security, you need to include the class annotated with #EnableGlobalMethodSecurity in the component classes used for loading the ApplicationContext.
For example, if the configuration class SecurityConfig is annotated with EnableGlobalMethodSecurity
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig { }
And the Service MessageService has a method using #PreAuthorize.
#Service
public class MessageService {
public String getHelloMessage() {
return "Hello!";
}
#PreAuthorize("hasRole('ADMIN')")
public String getGoodbyeMessage() {
return "Goodbye!";
}
}
Then you need to include both of those classes in the MessageServiceTest and you can use the security testing annotations.
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = {SecurityConfig.class, MessageService.class})
public class MessageServiceTest {
#Autowired
MessageService messageService;
#Test
public void helloMessageReturnsHello() {
assertThat(messageService.getHelloMessage()).isEqualTo("Hello!");
}
#Test(expected = AuthenticationCredentialsNotFoundException.class)
public void goodbyeMessageWithoutUserThrowsException() {
messageService.getGoodbyeMessage();
}
#WithMockUser(roles = "ADMIN")
#Test
public void goodbyeMessageWithAdminReturnsGoodbye() {
assertThat(messageService.getGoodbyeMessage()).isEqualTo("Goodbye!");
}
}
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;
i have the following issue with spring. I have a webapp and a domain project. the domain project contains a studentService which should be injected through autowiring in a class of the webapp. I've added and to the appcontext.xml.
this is the class from the webapp:
#Component
public class JSONToDomainObjects
{
#Autowired
private StudentService studentService;
private void bindSubmissionValuesToDomainObjects(Integer userKey) throws Exception
{
Student student = studentService.getStudentBySlNumber(userKey);
}
}
then the studentservice:
#Service
public class StudentService
{
..
}
So once I startup my app I see that the studentService is null, but when I get the appcontext and invoke the method getBean("studentService") than a studentservice instance is returned. I use spring 3.0.5. Does anybody have a clue why the autowiring fails?
cheers,
Michael
Why don't you use dependency injection in your testclasses as well? Something like this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations={"appcontext.xml"})
public final class JSONToDomainObjectsTests {
private StudentService service;
#Autowired
public void setService(StudentService service) {
this.service= service;
}
#Test
public void testJSONToDomain() {
service.foo();
}
}
Are you using <context:annotation-config/> in your appcontext.xml?