Recently I have added Spring Security to my Spring Boot project using the following class:
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class MySecurityConfig {
}
as result, by default all my URLs are now protected with authentication and a self-generated password.
The problem is that all tests in a #WebMvcTest class that I used for unit-testing a controller:
#RunWith(SpringRunner.class)
#WebMvcTest(SomeController.class)
public class SomeControllerTest {...}
are now failing everywhere because of lack of authorization.
Question: can I tell #Test methods to ignore authorization so they keep succeeding as before?
How can I prevent the #EnableWebSecurity config class from being picked on a specific #WebMvcTest unit testing class?
I would like the tests already in place to be able to still go through and to test the authentication features separately later on.
So far I have tried to use a nested config class in the testing class in order to exclude security configs:
#RunWith(SpringRunner.class)
#WebMvcTest(SomeController.class)
public class SomeControllerTest {
#Configuration
#EnableAutoConfiguration(exclude = { SecurityAutoConfiguration.class})
static class ContextConfiguration { }
....}
but it seems not to work.
NOTE : I am using Spring Boot 1.5.8
For me in Spring Boot 2.2.4 (JUnit5) the below seems to have worked and bypass the security filter.
#ExtendWith(SpringExtension.class)
#WebMvcTest(SomeController.class)
#AutoConfigureMockMvc(addFilters = false)
public class SomeControllerTest {
...
Note: this simply disables any filters in the SpringSecurity configuration. It won't disable the security completely. In other words it will still bootstrap security without loading any filters.
You can set secure=false in the #WebMvcTest annoation.
It will skip the spring security MockMvc auto configuration in your Test
#WebMvcTest(controllers = SomeController.class, secure = false)
public class SomeControllerTest {
Note by the author:
As of 2021, this answer has been obsolete for a few years and it probably won't work for you.
In Spring Boot 2.2.6, #WebMvcTest is meta annotated with #AutoConfigureWebMvc which auto-configure org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration as you can see in spring.factories of spring-boot-test-autoconfigure.jar
So you just have to exclude SecurityAutoConfiguration in your test to disable Spring Security :
#WebMvcTest(excludeAutoConfiguration = SecurityAutoConfiguration.class)
In Spring Boot 2.4 both secure flags were removed and none of the answers here actually work.
I ended up excluding all the security myself and wrapping it around in a custom annotation.
import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration;
import org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.core.annotation.AliasFor;
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
import java.lang.annotation.*;
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#WebMvcTest(excludeFilters = {#ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = WebSecurityConfigurer.class)},
excludeAutoConfiguration = {SecurityAutoConfiguration.class,
SecurityFilterAutoConfiguration.class,
OAuth2ClientAutoConfiguration.class,
OAuth2ResourceServerAutoConfiguration.class})
public #interface UnsecuredWebMvcTest {
#AliasFor(annotation = WebMvcTest.class, attribute = "controllers")
Class<?>[] value() default {};
#AliasFor(annotation = WebMvcTest.class, attribute = "controllers")
Class<?>[] controllers() default {};
}
With Spring Security 4+, I find #WithMockUser annotation to be very handy. It provides a mock user and password to test spring security methods annotated with #PreAuthorize or #PostAuthorize. All you need to do is annotate the test method with #WithMockUser. The default role for the user is USER. You can override the default username and role too.
//default
#Test
#WithMockUser
public void getProfile() {
//your test here
}
//with username and roles
#Test
#WithMockUser(username = "john", roles={"ADMIN"})
public void getProfile() {
//your test here
}
NOTE: This annotation can be used for classes.
#WithMockUser(username = "john", roles={"ADMIN"})
public class UsersAdminSecurityTest {
}
This worked for me, using spring boot 2.3.1.
#ExtendWith(SpringExtension.class)
#WebMvcTest(SomeController.class)
#AutoConfigureMockMvc(addFilters = false)
public class SomeControllerTest {
}
I understand this is a specific question for Spring Boot 1.5 but seems a bit old. In order to have a successful OAuth2 secured controller unit test running I applied the following steps, kindly notice I used Spring Boot 2.2.6, Gradle 5.x, and JUnit 5. This mechanism should work the same way deprecated ones based on #AutoConfigureMockMvc(secure = false) or #WebMvcTest(controllers = SomeController.class, secure = false)
This is for a REST API project that is secured (OAuth2) using Microsoft's Azure Active Directory, but essentially this testing strategy should work for any OIDC, OAuth2 configuration.
The trick is to have a Controller test file and annotate it with #WebMvcTest annotation, however, the following parameters are required:
#WebMvcTest(
value = YourController.class
// this disables loading up the WebSecurityConfig.java file, otherwise it fails on start up
, useDefaultFilters = false
// this one indicates the specific filter to be used, in this case
// related to the GreetController we want to test
, includeFilters = {
#ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
value = YourController.class
)
}
)
Here the configurations that make the test run successfully.
build.gradle
plugins {
id 'org.springframework.boot' version '2.2.6.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
}
group = 'com.grailscoder'
version = '0.0.1-SNAPSHOT'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
ext {
set('azureVersion', "2.2.4")
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'com.microsoft.azure:azure-active-directory-spring-boot-starter'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.2'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.5.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.5.2'
}
dependencyManagement {
imports {
mavenBom "com.microsoft.azure:azure-spring-boot-bom:${azureVersion}"
}
}
test {
useJUnitPlatform()
}
GreetController.java
package com.grailscoder.controller;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
#RequiredArgsConstructor
#RestController
public class GreetController {
#GetMapping("/greets")
#PreAuthorize("hasRole('ROLE_USER')") // This is validating against Active Directory's User role granted to the
// current user.
#ResponseStatus(HttpStatus.OK)
public String getGreetMessage() {
return "Greets from secret controller";
}
}
WebSecurityConfig.java
package com.grailscoder.config;
import com.microsoft.azure.spring.autoconfigure.aad.AADAppRoleStatelessAuthenticationFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
#RequiredArgsConstructor
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final AADAppRoleStatelessAuthenticationFilter aadAuthFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
http.authorizeRequests()
.antMatchers("/", "/index.html", "/public").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(aadAuthFilter, UsernamePasswordAuthenticationFilter.class);
}
}
application.properties
azure.activedirectory.client-id=xxxxx-AD-client-id-goes-here
azure.activedirectory.session-stateless=true
GreetControllerTest.java
package com.grailscoder.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
#WebMvcTest(
value = GreetController.class
// this disables loading up the WebSecurityConfig.java file, otherwise it fails on start up
, useDefaultFilters = false
// this one indicates the specific filter to be used, in this case
// related to the GreetController we want to test
, includeFilters = {
#ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
value = GreetController.class
)
}
)
class GreetControllerTest {
#Autowired
MockMvc mockMvc;
#Autowired
ObjectMapper objectMapper;
#BeforeEach
void setUp() {
// add setup stuff here
}
#Test
#WithMockUser
void testGreet() throws Exception {
ResultActions result = mockMvc.perform(get("/greets"))
.andExpect(status().isOk());
System.out.println(result.andReturn().getResponse().getContentAsString());
}
}
I understand in order to have a similar test JUnit 4 based with a completely different approach, the following test could be used as a reference (but I haven't tried): https://github.com/spring-projects/spring-security/blob/master/samples/boot/oauth2resourceserver/src/test/java/sample/OAuth2ResourceServerControllerTests.java
In my case, for Spring Boot version 2.5.4, I'm able to bypass Jwt security by setting useDefaultFilters = false in #WebMvcTest
#WebMvcTest(controllers = YourController.class, useDefaultFilters = false)
public class YourControllerTest {
// Test cases
}
#AutoConfigureMockMvc(addFilters = false)
Just adding addFilters = false resolved this.
I solved the problem by using following annotations and properties:
#WebMvcTest(controllers =
SomeController.class,
excludeAutoConfiguration = {
MySecurityConfig.class,
ManagementWebSecurityAutoConfiguration.class,
SecurityAutoConfiguration.class
}
)
#ContextConfiguration(classes = SomeController.class)
public class SomeControllerTest {
}
NOTE: I' using spring boot 2.6.6, so secure=false didn't work for me!
#AutoConfigureMockMvc(secure = false) does not work because secure is deprecated
what works:
#AutoConfigureMockMvc(addFilters = false)
does not completely disable spring security just bypass its filter chain.
or
#WebMvcTest(excludeAutoConfiguration = {SecurityAutoConfiguration.class})
(and if you are using Spring Boot actuator:
#WebMvcTest(excludeAutoConfiguration = {SecurityAutoConfiguration.class,
ManagementWebSecurityAutoConfiguration.class})
)
Related
I'm working on a REST API using Spring Boot. Currently, the V1 of the API is complete. So, I'm implementing Spring Security to manage authentication and authorization.
Since I've implemented Spring Security, my JUnit Jupiter tests does not work (no one works).
I searched a lot a solution on internet, but all answers I found are for JUnit4 and not JUnit5 (so I don't have all required classes).
I got the classical "Fail to load ApplicationContext" error, but I don't know how to solve it.
Can you help me?
Here is my code for one class (UserController):
gradle.build:
plugins {
id 'jacoco'
id 'org.springframework.boot' version '2.6.0'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web:2.5.6'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa:2.5.6'
implementation 'org.projectlombok:lombok:1.18.22'
annotationProcessor 'org.projectlombok:lombok:1.18.22'
developmentOnly 'org.springframework.boot:spring-boot-devtools:2.5.6'
testImplementation 'org.springframework.boot:spring-boot-starter-test:2.5.6'
implementation 'com.h2database:h2'
runtimeOnly 'com.h2database:h2'
}
test {
systemProperty 'spring.profiles.active', 'test'
useJUnitPlatform()
finalizedBy jacocoTestReport
}
Application:
#SpringBootApplication
public class BackendApplication {
public static void main(String[] args) {
SpringApplication.run(BackendApplication.class, args);
}
}
UserController sample:
#RestController
#RequestMapping("/api/v1/users")
public class UserController extends AbstractCrudController<User, Long> {
#Autowired
public UserController(CrudService<User, Long> service) { super(service); }
#GetMapping("")
#Override
#Secured({ "ROLE_XXXXX" })
public ResponseEntity<ResponseListDto<User, Long>> findAll() {
return super.findAll();
}
// ...
}
MockedUserControllerTest sample:
#SpringBootTest
public class MockedUserControllerTest {
#Mock
private UserService service;
#InjectMocks
private UserController controller;
private static User user;
private static List<User> users;
#BeforeAll
public static void beforeAll() {
user = new User();
user.setId(1L);
user.setUsername("A user name");
user.setFirstname("First-Name");
user.setLastname("Last-Name");
user.setPassword("A Gre4t P4ssw0rd!");
user.setMail("first-name.last-name#mail.com");
user.setBirthDate(Date.valueOf("1980-01-15"));
user.setKey("A-key");
user.setNewsletter(Boolean.TRUE);
users = List.of(user);
}
#Test
public void testFindAll() {
when(service.findAll()).thenReturn(users);
assertEquals(new ResponseEntity<>(new ResponseListDto<>(users, null, null), HttpStatus.OK),
controller.findAll());
}
//...
}
Thank you in advance for looking my problem.
For a #SpringBootTest you should use #MockBean annotation, because the Spring context will load in order to run the tests. The loaded context will create mocked beans from the dependencies being annotated by #MockBean and it will inject them into that service, which is being tested.
For pure unit tests the #SpringBootTest annotation should be skipped and Mockito (#Mock annotation) can be used. Spring context will not load in this case, the test will focus on that specific class you are testing. With the created Mocks, you can control the behaviour of dependencies, you can arrange different scenarios for your test.
After some other basic researches (how to write tests with junit5 and mockito), I solved my problem myself.
Here is the answer which helped me: https://stackoverflow.com/a/40962941/13523752
What I wanted is a class test only for the controller I specified. So I didn't need the ApplicationContext. That oriented my research.
Note: I'll do other test classes to test all the process. In this tests I'll need the ApplicationContext.
On my test class, I removed the annotation #SpringBootTest to replace it by #ExtendWith(MockitoExtension.class).
The next thing I did is in the #BeforeAll method I have. I had MockitoAnnotations.initMocks(MockedUserControllerTests.class) to load the mocks I annotated.
Now my test work. I only have to extend this solution on all other mocked test classes.
A sample of the test class I have now:
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.jupiter.MockitoExtension;
#ExtendWith(MockitoExtension.class)
public class MockedUserControllerTest {
#Mock
UserService service;
#InjectMocks
UserController controller;
// ...
#BeforeAll
public static void beforeAll() {
MockitoAnnotations.initMocks(MockedUserControllerTest.class);
// ...
}
// ...
}
My SpringBoot app has Hystrix enabled with fallback defined for some of the Feign clients and undefined for the rest them.
Now, I wanted to disable Hystrix for the ones that did not have a fallback defined as yet. So I followed the steps listed in [paragraph 7.4] https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-feign.html which is to create a separate Feign configuration with a vanilla Feign.Builder. However adding the new #Bean Feign.Builder disables my Hystrix functionality across all Feign clients which I don't want. If I remove the #Bean Feign.Builder, Hystrix fallback kicks in like usual in myhystrixclient. A similar SO question here How to disable hystrix in one of multiple feign clients is still open. What am I doing wrong?
public class MyFeignClientConfiguration {
#Bean
public FeignErrorDecoder feignErrorDecoder() {
return new FeignErrorDecoder();
}
#Bean
#Scope("prototype")
public Feign.Builder feignBuilder() {
return Feign.builder();
}
}
My Feign Client looks like below:
#FeignClient(name = "myregularclient", configuration = MyFeignClientConfiguration.class)
public interface MyRegularClient {
//my APIs here
}
My Hystrix Feign Configuration looks like the below:
public class MyFeignClientHystrixConfiguration {
#Bean
public FeignErrorDecoder feignErrorDecoder() {
return new FeignErrorDecoder();
}
}
And here is my Feign client where Hystrix fallback is implemented
#FeignClient(name = "myhystrixclient", configuration = MyFeignClientHystrixConfiguration.class, fallback = MyFallbackService.class)
public interface MyHystrixClient {
//my APIs here
}
UPDATE
Adding my Application.java for further review of the component scan aspects.
#ComponentScan(basePackages ="com.demo.xyz")
#EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class,
MetricFilterAutoConfiguration.class,
MetricRepositoryAutoConfiguration.class})
#EnableDiscoveryClient
#EnableFeignClients
#EnableCircuitBreaker
public class MyApplication {
/** Start the app **/
}
I managed to reproduce problem on Spring Cloud vDalston.SR5 and it seems I found a solution. It doesn't brake other feign clients which use hystrix. Tested it manually and with integration tests.
Create feign client without hystrix. Notice that configuration class is annotated with #Configuration annotation.
#FeignClient(name = "withoutHystrix",
url = "conrad.fake",
fallback = FeignClientWithoutHystrix.FallbackThatShouldNotOccur.class,
configuration = FeignClientWithoutHystrixConfig.class)
public interface FeignClientWithoutHystrix {
#RequestMapping(method = RequestMethod.GET, value = "/fake/url")
String getFromFakeUrl();
#Component
class FallbackThatShouldNotOccur implements FeignClientWithoutHystrix {
private final Logger log = LoggerFactory.getLogger(this.getClass());
#Override
public String getFromFakeUrl() {
log.error("This fallback shouldn't occur");
return "Fallback";
}
}
}
#Configuration
public class FeignClientWithoutHystrixConfig {
#Bean
public Feign.Builder feignBuilder() {
return Feign.builder();
}
}
Exclude feign client configuration from #ComponentScan with excludeFilters:
#EnableFeignClients
#SpringBootApplication
#ComponentScan(basePackages = "konrad",
excludeFilters = {#ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = FeignClientWithoutHystrixConfig.class)})
public class CloudClient {
public static void main(String[] args) {
SpringApplication.run(CloudClient.class, args);
}
}
Enable hystrix
feign:
hystrix:
enabled: true
If you want to check anything or run tests, this is my repository https://github.com/MrJavaNoHome/spring-cloud-client
please try this configuration:
import feign.Feign;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
#Configuration
public class MyFeignClientConfiguration {
#Bean
#Scope("prototype")
public Feign.Builder feignBuilder() {
return Feign.builder();
}
}
And:
import feign.hystrix.HystrixFeign;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
#Configuration
public class MyFeignClientHystrixConfiguration {
#Bean
#Scope("prototype")
public HystrixFeign.Builder feignBuilder() {
return HystrixFeign.builder();
}
}
feign-hystrix was a part of spring-cloud-starter-openfeign with version less than 3.0.0.
Starting from 3.0.0 , feign-hystrix no longer included
I am learning Spring (currently its AOP framework). Even though all sources I've read say that to enable AOP one needs to use #EnableAspectJAutoProxy annotation (or its XML counterpart) my code seems to work with annotation commented out. Is that because I use Lombok or Spring Boot (v. 1.5.9.RELEASE, dependent on Spring v. 4.3.13.RELEASE)?
Minimal example follows:
build.gradle
buildscript {
ext {
springBootVersion = '1.5.9.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
group = 'lukeg'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter')
compileOnly('org.projectlombok:lombok')
compile("org.aspectj:aspectjweaver:1.8.11")
testCompile('org.springframework.boot:spring-boot-starter-test')
}
ApplicationConfiguration.java (note the AOP annotation is commented out)
package lukeg;
import org.springframework.context.annotation.*;
#Configuration
#ComponentScan
//#EnableAspectJAutoProxy
public class ApplicationConfiguration {
#Bean
TestComponent testComponent() {
return new TestComponent();
}
}
LearnApplication.java
package lukeg;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
#SpringBootApplication
public class LearnApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(LearnApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfiguration.class);
TestComponent testComponent = context.getBean(TestComponent.class);
System.out.println(""+testComponent);
}
}
LoggerHogger.java
package lukeg;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
#Component
#Aspect
public class LoggerHogger {
#Pointcut("execution(* lukeg*.*.toString(..))")
public void logToString() {}
#Before("logToString()")
public void beforeToString () {
System.out.println("Before toString");
}
}
TestComponent.java
package lukeg;
import lombok.Data;
#Data
public class TestComponent {
}
The #SpringBootApplication annotation contains the #EnableAutoConfiguration annotation. This autoconfiguration is one of the attractions of Spring Boot and makes configuration simpler. The auto configuration uses #Conditional type annotations (like #ConditionalOnClass and #ConditionalOnProperty) to scan the classpath and look for key classes that trigger the loading of 'modules' like AOP.
Here is an example AopAutoConfiguration.java
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.Advice;
import org.aspectj.weaver.AnnotatedElement;
#Configuration
#ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class,
AnnotatedElement.class })
#ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
#Configuration
#EnableAspectJAutoProxy(proxyTargetClass = false)
#ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false)
public static class JdkDynamicAutoProxyConfiguration {
}
#Configuration
#EnableAspectJAutoProxy(proxyTargetClass = true)
#ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
public static class CglibAutoProxyConfiguration {
}
}
As you can see, if you add one of the above aop classes to your class path (or property), Spring will detect it and effectively behave as if you had the #EnableAspectJAutoProxy annotation on your main class.
Your project has a file LoggerHogger which has an #Aspect.
Situation and Problem: In Spring Boot, how can I inject one or more mocked classes/beans into the application to do an integration test? There are a few answers on StackOverflow, but they are focused on the situation before Spring Boot 1.4 or are just not working for me.
The background is, that in the code bellow the implementation of Settings relies on third party servers and other external systems. The functionality of Settings is already tested in a unit test, so for a full integration test I want to mock out the dependency to these servers or system and just provide dummy values.
MockBean will ignore all existing bean definitions and provide a dummy object, but this object doesn't provide a method behavior in other classes that inject this class. Using the #Before way to set the behavior before a test doesn't influence the injected object or isn't injected in other application services like AuthenticationService.
My question: How can I inject my beans into the application context?
My test:
package ch.swaechter.testapp;
import ch.swaechter.testapp.utils.settings.Settings;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.junit4.SpringRunner;
#TestConfiguration
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {MyApplication.class})
public class MyApplicationTests {
#MockBean
private Settings settings;
#Before
public void before() {
Mockito.when(settings.getApplicationSecret()).thenReturn("Application Secret");
}
#Test
public void contextLoads() {
String applicationsecret = settings.getApplicationSecret();
System.out.println("Application secret: " + applicationsecret);
}
}
And bellow a service that should use the mocked class, but doesn't receive this mocked class:
package ch.swaechter.testapp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
public class AuthenticationServiceImpl implements AuthenticationService {
private final Settings settings;
#Autowired
public AuthenticationServiceImpl(Settings settings) {
this.settings = settings;
}
#Override
public boolean loginUser(String token) {
// Use the application secret to check the token signature
// But here settings.getApplicationSecret() will return null (Instead of Application Secret as specified in the mock)!
return false;
}
}
Looks like you are using Settings object before you specify its mocked behavior.
You have to run
Mockito.when(settings.getApplicationSecret()).thenReturn("Application Secret");
during configuration setup. For preventing that you can create special configuration class for test only.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {MyApplication.class, MyApplicationTest.TestConfig.class})
public class MyApplicationTest {
private static final String SECRET = "Application Secret";
#TestConfiguration
public static class TestConfig {
#Bean
#Primary
public Settings settingsBean(){
Settings settings = Mockito.mock(Settings.class);
Mockito.when(settings.getApplicationSecret()).thenReturn(SECRET);
Mockito.doReturn(SECRET).when(settings).getApplicationSecret();
return settings;
}
}
.....
}
Also I would recommend you to use next notation for mocking:
Mockito.doReturn(SECRET).when(settings).getApplicationSecret();
It will not run settings::getApplicationSecret
When you annotate a field with #MockBean, spring will create a mock of the annotated class and use it to autowire all beans of the application context.
You must not create the mock yourself with
Settings settings = Mockito.mock(Settings.class);
this would create a second mock, leading to the described problem.
Solution :
#MockBean
private Settings settings;
#Before
public void before() {
Mockito.when(settings.getApplicationSecret()).thenReturn("Application Secret");
}
#Test
public void contextLoads() {
String applicationsecret = settings.getApplicationSecret();
System.out.println("Application secret: " + applicationsecret);
}
We are currently using Spring Boot to connect to a mocked local instance of Amazon SQS. The application itself is working when run, but we would like to try and test the SQS Config class, if possible and if it makes sense.
Here is the configuration class. All properties are pulled from the typical application.properties file when the Spring application itself is run.
import com.amazonaws.services.sqs.AmazonSQSAsync;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.aws.core.env.ResourceIdResolver;
import org.springframework.cloud.aws.messaging.core.QueueMessagingTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class AWSSQSConfig {
#Value("${aws.sqs.endpoint}")
private String AWSSqsEndpoint;
// Producer QueueMessageTemplate
#Bean
public QueueMessagingTemplate queueMessagingTemplate(AmazonSQSAsync amazonSqs, ResourceIdResolver resourceIdResolver) {
if (!AWSSqsEndpoint.isEmpty())
amazonSqs.setEndpoint(AWSSqsEndpoint);
return new QueueMessagingTemplate(amazonSqs, resourceIdResolver);
}
}
Here is the test class. We are attempting to pass the configuration in via TestPropertySource, but they don't actually seem to get to the AWSSQSConfig class. AWSSqsEndpoint inside the instance of the class is always NULL.
import com.amazonaws.services.sqs.AmazonSQSAsync;
import com.lonewolf.formsbuilder.config.AWSSQSConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.aws.core.env.ResourceIdResolver;
import org.springframework.cloud.aws.messaging.core.QueueMessagingTemplate;
import org.springframework.test.context.TestPropertySource;
import static org.junit.Assert.assertNotNull;
#RunWith(MockitoJUnitRunner.class)
#SpringBootTest
#TestPropertySource(properties = {
"cloud.aws.region.static=us-east-1",
"cloud.aws.credentials.accessKey=zzzzz",
"cloud.aws.credentials.secretKey=zzzzzz",
"aws.sqs.endpoint = http://localhost:9324",
"aws.sqs.requestQueue = CreateSchemaRequest",
"aws.sqs.responseQueue = CreateSchemaResponse"
})
public class AWSSQSConfigTests {
#Mock
private AmazonSQSAsync amazonSqs;
#Mock
private ResourceIdResolver resourceIdResolver;
#Test
public void contextLoads() {
AWSSQSConfig config = new AWSSQSConfig();
QueueMessagingTemplate queueMessagingTemplate = config.queueMessagingTemplate(amazonSqs, resourceIdResolver);
assertNotNull("The response body must not be null", queueMessagingTemplate);
}
}
Is this a chicken and the egg situation, where the spring framework actually needs to run first to inject those config values? Do we need an integration test here instead?
EDIT with working solution...
Using the accepted answer, here is my working test! I was able to remove my dependency of the Spring framework.
#RunWith(MockitoJUnitRunner.class)
public class AWSSQSConfigTests {
#Mock
private AmazonSQSAsync amazonSqs;
#Mock
private ResourceIdResolver resourceIdResolver;
#InjectMocks
private AWSSQSConfig config;
#Before
public void setup() {
ReflectionTestUtils.setField(config, "AWSSqsEndpoint", "http://fake");
}
#Test
public void contextLoads() {
QueueMessagingTemplate queueMessagingTemplate = config.queueMessagingTemplate(amazonSqs, resourceIdResolver);
assertNotNull("The response body must not be null", queueMessagingTemplate);
}
}
Have you tried injecting mock to your class (or autowire it), and then setting that field it using ReflectionTestUtils? This is a nice test utils class that Spring provides that allows you to do something like what you want without doing code modifications.
I mean something like this:
#InjectMocks
private AWSSQSConfig awssqsConfig;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(awssqsConfig, "AWSSqsEndpoint", "putYourEndpointHere");
}