How can I create a bypass #Preauthorize so that I can test in local with out calling the actual because annotation will be loaded before the class loads ?
#RestController
#RequestMapping("/test")
public class ResourceController {
#RequestMapping(method = GET)
#PreAuthorize
#ResponseBody
public String message(){ return "Hello World"; }
You can use spring-security-test to achieve this.
pom.xml
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<version>5.3.3.RELEASE</version>
<scope>test</scope>
</dependency>
Supposedly your controller looks like:
#PreAuthorize("hasRole('ROLE_ADMIN')")
public User createUser(final User user) {
......
}
And your test can look like:
public class MyControllerTests {
private MockMvc mvc;
#BeforeEach
void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
#Test
void testCreateWithProperPermission() throws Exception {
final User user = new User();
user.setName("Test");
final MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.post("/v1/foo/").with(user("foo").roles("ADMIN"))
.content(new ObjectMapper().writeValueAsString(user))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
final String responseBody = mvcResult.getResponse().getContentAsString();
final User created = new ObjectMapper().readValue(responseBody, User.class);
// verify the saved entity's data is correct
assertThat(created).isNotNull();
assertThat(created)
.hasFieldOrPropertyWithValue("name", user.getName());
}
You can have a test profile in your code which you then activate when running tests against the code. You can then use the predefined user and password in your tests.
#Configuration
public class TestConfig {
#EnableWebSecurity
#Profile("test")
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("user").password("password").roles("ROLE1", "ROLE2", "ROLE3").build());
return manager;
}
}
}
Related
I am trying to use
junit.jupiter.execution.parallel.mode.default=concurrent
along with #MockBean from Spring Boot. However, the tests starts to fail when I set to concurrent mode. I tried setting
#MockBean(reset = MockReset.NONE)
However, it also does not help. Seems like mocked bean is reinitialized / reset even though MockReset.NONE is set.
Is that possible to use #MockBean allow with concurrent mode or is it a known limitation?
Bean I am mocking:
#Service
public class SampleService {
public Mono<String> processCall(String call) {
return Mono.just("ok");
}
}
The controller I am testing:
#RestController
#RequiredArgsConstructor
public class SampleController {
private final SampleService sampleService;
#PostMapping("/call")
public Mono<String> processCall(#RequestBody String body) {
return sampleService.processCall(body);
}
}
And tests:
#ExtendWith(SpringExtension.class)
#WebFluxTest(SampleController.class)
#ContextConfiguration(classes = {SampleController.class})
class SampleTest {
#MockBean(reset = MockReset.NONE)
private SampleService sampleService;
#Autowired
private WebTestClient webTestClient;
#Test
void givenSampleServiceWorksFineExpectOkResponse() {
String randomBody = "1234";
when(sampleService.processCall(randomBody)).thenReturn(Mono.just("ok"));
callService(randomBody).expectStatus().isOk().expectBody(String.class).isEqualTo("ok");
}
#Test
void givenSampleServiceFailedExpectErrorResponse() {
String randomBody = "9876";
when(sampleService.processCall(randomBody))
.thenReturn(Mono.error(new RuntimeException("error")));
callService(randomBody).expectStatus().is5xxServerError();
}
private ResponseSpec callService(String body) {
return webTestClient
.post()
.uri(
uriBuilder ->
uriBuilder
.path("/call")
.build())
.bodyValue(body)
.exchange();
}
}
I set concurrent mode for Jupiter in file junit-platform.properties:
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.config.strategy=dynamic
I created Spring boot application with the following configuration:
Spring boot 2.1.0.RELEASE
OpenJdk 11
I have an AuditConfiguration class in my project that looks like:
#Configuration
#EnableJpaAuditing(auditorAwareRef = "auditorProvider")
public class AuditConfiguration {
#Bean
public AuditorAware<String> auditorProvider() {
return new AuditorAwareImpl();
}
class AuditorAwareImpl implements AuditorAware<String> {
#Override
public Optional<String> getCurrentAuditor() {
Principal principal =
SecurityContextHolder.getContext().getAuthentication();
return Optional.of(principal.getName());
}
}
}
and SecurityContextHolder.getContext().getAuthentication() always returns anonymousUser.
However, the following code returns the correct user name.
#RestController
#RequestMapping("/history")
public class HistoryEndpoint {
#RequestMapping(value = "/username", method = RequestMethod.GET)
#ResponseBody
public String currentUserName(Principal principal) {
return principal.getName();
}
}
I need your help for resolving this issue.
I got authenticared user using following class. i had problem with JPA Auditing.
#CreatedBy always saved null. then i tried to get authenticated user SecurityContextHolder.getContext().getAuthentication() using this method. that method returned annonymousUser. however my issue is fixed.
#ManagedBean
#EnableJpaAuditing
public class SpringSecurityAuditorAware implements AuditorAware<String> {
private final HttpServletRequest httpServletRequest;
public SpringSecurityAuditorAware(HttpServletRequest httpServletRequest) {
this.httpServletRequest = httpServletRequest;
}
#Override
public Optional<String> getCurrentAuditor() {
return Optional.ofNullable(httpServletRequest.getUserPrincipal())
.map(Principal::getName);
}
}
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'm using Spring Boot 1.4.0.RELEASE with spring-boot-starter-batch, spring-boot-starter-aop and spring-retry
I have a Spring Integration test that has a #Service which is mocked at runtime. I've noticed that if the #Service class contains any #Retryable annotations on its methods, then it appears to interfere with Mockito.verify(), I get a UnfinishedVerificationException. I presume this must be something to do with spring-aop? If I comment out all #Retryable annotations in the #Service then verify works ok again.
I have created a github project that demonstrates this issue.
It fails in sample.batch.MockBatchTestWithRetryVerificationFailures.batchTest() at validateMockitoUsage();
With something like:
12:05:36.554 [main] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - After test method: context [DefaultTestContext#5ec0a365 testClass = MockBatchTestWithRetryVerificationFailures, testInstance = sample.batch.MockBatchTestWithRetryVerificationFailures#5abca1e0, testMethod = batchTest#MockBatchTestWithRetryVerificationFailures, testException = org.mockito.exceptions.misusing.UnfinishedVerificationException:
Missing method call for verify(mock) here:
-> at sample.batch.service.MyRetryService$$FastClassBySpringCGLIB$$7573ce2a.invoke(<generated>)
Example of correct verification:
verify(mock).doSomething()
However I have another class (sample.batch.MockBatchTestWithNoRetryWorking.batchTest()) with a mocked #Service that doesn't have any #Retryable annotation and verify works fine.
What am I doing wrong?
In my pom.xml I have the following:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.0.RELEASE</version>
</parent>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
...
Then all the related Java Classes
#SpringBootApplication
#EnableBatchProcessing
#Configuration
#EnableRetry
public class SampleBatchApplication {
#Autowired
private JobBuilderFactory jobs;
#Autowired
private StepBuilderFactory steps;
#Autowired
private MyRetryService myRetryService;
#Autowired
private MyServiceNoRetry myServiceNoRetry;
#Bean
protected Tasklet tasklet() {
return new Tasklet() {
#Override
public RepeatStatus execute(StepContribution contribution,
ChunkContext context) {
myServiceNoRetry.process();
myRetryService.process();
return RepeatStatus.FINISHED;
}
};
}
#Bean
public Job job() throws Exception {
return this.jobs.get("job").start(step1()).build();
}
#Bean
protected Step step1() throws Exception {
return this.steps.get("step1").tasklet(tasklet()).build();
}
public static void main(String[] args) throws Exception {
// System.exit is common for Batch applications since the exit code can be used to
// drive a workflow
System.exit(SpringApplication
.exit(SpringApplication.run(SampleBatchApplication.class, args)));
}
#Bean
ResourcelessTransactionManager transactionManager() {
return new ResourcelessTransactionManager();
}
#Bean
public JobRepository getJobRepo() throws Exception {
return new MapJobRepositoryFactoryBean(transactionManager()).getObject();
}
}
#Service
public class MyRetryService {
public static final Logger LOG = LoggerFactory.getLogger(MyRetryService.class);
#Retryable(maxAttempts = 5, include = RuntimeException.class, backoff = #Backoff(delay = 100, multiplier = 2))
public boolean process() {
double random = Math.random();
LOG.info("Running process, random value {}", random);
if (random > 0.2d) {
throw new RuntimeException("Random fail time!");
}
return true;
}
}
#Service
public class MyServiceNoRetry {
public static final Logger LOG = LoggerFactory.getLogger(MyServiceNoRetry.class);
public boolean process() {
LOG.info("Running process that doesn't do retry");
return true;
}
}
#ActiveProfiles("Test")
#ContextConfiguration(classes = {SampleBatchApplication.class, MockBatchTestWithNoRetryWorking.MockedRetryService.class}, loader = AnnotationConfigContextLoader.class)
#RunWith(SpringRunner.class)
public class MockBatchTestWithNoRetryWorking {
#Autowired
MyServiceNoRetry service;
#Test
public void batchTest() {
service.process();
verify(service).process();
validateMockitoUsage();
}
public static class MockedRetryService {
#Bean
#Primary
public MyServiceNoRetry myService() {
return mock(MyServiceNoRetry.class);
}
}
}
#ActiveProfiles("Test")
#ContextConfiguration(classes = { SampleBatchApplication.class,
MockBatchTestWithRetryVerificationFailures.MockedRetryService.class },
loader = AnnotationConfigContextLoader.class)
#RunWith(SpringRunner.class)
public class MockBatchTestWithRetryVerificationFailures {
#Autowired
MyRetryService service;
#Test
public void batchTest() {
service.process();
verify(service).process();
validateMockitoUsage();
}
public static class MockedRetryService {
#Bean
#Primary
public MyRetryService myRetryService() {
return mock(MyRetryService.class);
}
}
}
EDIT: Updated question and code based on a sample project I put together to show the problem.
So after looking at a similar github issue for spring-boot
I found that there is an extra proxy getting in the way. I found a nasty hack by unwrapping the aop class by hand, makes verification work, ie:
#Test
public void batchTest() throws Exception {
service.process();
if (service instanceof Advised) {
service = (MyRetryService) ((Advised) service).getTargetSource().getTarget();
}
verify(service).process();
validateMockitoUsage();
}
Hopefully, this can be fixed similar to the above github issue. I'll raise an issue and see how far I get.
EDIT: Raised the github issue
After I've seen #Joel Pearsons answer, and especially the linked GitHub issue, I worked around this by temporarily using a static helper method that unwraps and verifies:
public static <T> T unwrapAndVerify(T mock, VerificationMode mode) {
return ((T) Mockito.verify(AopTestUtils.getTargetObject(mock), mode));
}
With this method the only difference in the test cases is the verification call. There is no overhead other than this:
unwrapAndVerify(service, times(2)).process();
instead of
verify(service, times(2)).process();
Actually, it was even possible to name the helper method like the actual Mockito method, so that you only need to replace the import, but I didn't like the subsequent confusion.
However, unwrapping shouldn't be required if #MockBean is used instead of mock() to create the mocked bean. Spring Boot 1.4 supports this annotation.
I really searched and followed the steps of creating a unit test class for spring MVC controller, however unit test is running with a green pass flag but the framework uses the original service class and it calls to the database. I mocked the class and used #InjectMocks together with MockitoAnnotations.initMocks(this). Still when the test runs, the controller uses original service object rather than the mocked object. I really appreciate if somebody can help me in this regards.
Here is UserManager(service class), UserRegisterController(controller), TestUserRegisterController (Test class) classes with a picture of the Eclipse package structure
Service :
#Service
public class UserManager {
protected Map<String, String> getAllCertificates() {
Map<String, String> allCertificates = new HashMap<String, String>();
//call to database
return allCertificates;
}
protected User getUser(int userId) {
//create session
User user = session.get(User.class, userId);
//close session
return user;
}
}
Controller :
#Controller
public class UserRegisterController {
#Autowired
private UserManager manager;
#InitBinder
public void initBinder(WebDataBinder binder) {
//do some work
}
#RequestMapping(value = "/user.html", method = RequestMethod.GET)
public ModelAndView getUser(#RequestParam(value="userId", defaultValue="-1") String userId) {
User user1;
user1 = this.manager.getUser(Integer.parseInt(userId));
if (user1 == null) {
user1 = new User();
}
ModelAndView view = new ModelAndView("User", "user1", user1);
view.addObject("allCertificatesMap", this.manager.getAllCertificates());
return view;
}
#ModelAttribute
public void setModelAttribute(Model model) {
model.addAttribute("PageHeader", "lable.pageHeader");
}
}
Test class :
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("test-spring-dispatcher-servlet.xml")
#WebAppConfiguration
public class TestUserRegisterController {
#Mock
private UserManager userManager;
#InjectMocks
private UserRegisterController userRegisterController;
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setUp() throws Exception {
// Process mock annotations
MockitoAnnotations.initMocks(this);
User user2 = new User();
user2.setUserId(10006);
user2.setUserName("Reza");
user2.setHobby("Quadcopter");
user2.setPhone("4032376295");
when(this.userManager.getUser(10006)).thenReturn(user2);
when(this.userManager.getAllCertificates()).thenReturn(new HashMap<String, String>());
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void getUser() {
try {
this.mockMvc.perform(get("/user.html").param("userId", "10006"))
.andExpect(status().isOk())
.andExpect(forwardedUrl("/WEB-INF/jsp/User.jsp"))
.andExpect(MockMvcResultMatchers.view().name("User"))
.andExpect(model().attributeExists("allCertificatesMap"));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Package hierarchy
Use #RunWith(MockitoJUnitRunner.class) to get the #InjectMocks and other annotations to work