Overiding a Autowired object on class construction - java

I have a class that uses an autowired properties object. These properties have some configuration required in order for my communications to work properly. In my unit tests I wrote a scenario in which communication should fail, by overriding the properties object in the class constructor as follows:
public class TokenRetriever{
#Autowired
private TokenRepository repository;
#Autowired
private Properties properties;
//custom constructor for me to override the properties
public TokenRetriever(Properties properties){
this.properties = properties;
}
private Token retrieveToken() {
Token token = null;
try {
//communication to an endpoint using properties
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return token;
}
public Token getAccessToken() throws NullAccessToken {
Token token;
token = repository.findTop1ByExpiresAtGreaterThanOrderByExpiresAtDesc(LocalDateTime.now());
if (token == null) token = this.retrieveToken();
if (token == null) throw new NullAccessToken("Could not retrieve any tokens");
return token;
}
}
And this is my unit test:
#Test
void ShouldNotRetrieveAToken() {
//this is the property i'm changing in order to force a failure
properties.setClientId("dummy");
tokenRetriever = new TokenRetriever(properties);
Exception exception = assertThrows(NullAccessToken.class,
() ->
tokenRetriever.getAccessToken()
);
String expectedMessage = "Could not retrieve any tokens";
String actualMessage = exception.getMessage();
assertTrue(actualMessage.contains(expectedMessage));
}
Which works just fine when I run the unit test. However, when I build the project this fails because the error is not thrown. I assume this is because the overriding is not working. I'm new to spring boot and junits, so this probably has to do with spring lifecycles. How can I accomplish the properties overide in order for my junit to pass?

Constructor injection Does injection only when the object create.
if you want create another object with different property object you must use Setter-based dependency injection.
there is setter-based injection documentation https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-setter-injection

You are mixing constructor and field injection.
It's recommended to use constructor injection where possible. You also will not need an annotation.
private final TokenRepository repository;
private final Properties properties;
public TokenRetriever(TokenRepository repository, Properties properties){
this.repository = repository;
this.properties = properties;
}

Related

Mock dependency with ApplicationContextRunner in SpringBoot Test result into UnsatisfiedDependencyException

I have following class, which publishes the spring event.
#Component
public class ApplicationReadyEventListener {
Boolean isHit = false;
#EventListener
public void handle(final ApplicationReadyEvent applicationReadyEvent) {
applicationReadyEvent.getSpringApplication().getClass().toGenericString()));
isHit = true; // This needs to be replaced with CustomLoggerComponent
}
}
As I need to publish the event and need to check failure and success event, I have following test:
#ExtendWith({SpringExtension.class})
class ApplicationReadyEventListenerTest {
private final ApplicationContextRunner runner = new ApplicationContextRunner();
//Success Test
#Test
void loggerShouldLogWhenApplicationIsReady() {
SpringApplciation application = new SpringApplication(ApplicationReadyEventListener.class);
application.setWebApplicationType(WebApplicationType.NONE);
final ApplicationReadyEvent event = new ApplicationReadyEvent(application, null, mock(ConfigurableApplicationContext.class));
runner.withBean(ApplicationReadyEventListener.class)
.run(context -> {
context.publishEvent(event);
final ApplicationReadyEventListener applicationStartedListener = context.getBean(ApplicationReadyEventListener.class);
MatcherAssert.assertThat(applicationStartedListener.isHit(), is(true));
});
}
//FailureTest
#Test
void shouldNotCallApplicationStarted() {
SpringApplciation application = new SpringApplication(ApplicationReadyEventListener.class);
application.setWebApplicationType(WebApplicationType.NONE);
final RuntimeException runtimeException = new RuntimeException("Some Error Occurred");
final ApplicationEvent event = new ApplicationFailedEvent(application, null, mock(ConfigurableApplicationContext.class), runtimeException);
runner.withBean(ApplicationReadyEventListener.class)
.run(context -> {
context.publishEvent(event);
final ApplicationReadyEventListener applicationStartedListener = context.getBean(ApplicationReadyEventListener.class);
MatcherAssert.assertThat(applicationStartedListener.isHit(), is(false));
});
}
}
This is working fine as of now as the class (ApplicationReadyEventListener) does not have any bean. I want to have a custom logger for this one, and instead of isHit, I would be checking the side effect of custom logger's method getting called.
However, I could not add any dependency, so I tried to isolate the problem by creating a separate application which contains the subject under test ApplicationReadyEvent and to have CustomLoggerBean created, used following one:
#Configuration
public class CustomLogMockProvider {
#Bean
public Logger logger() {
return Mockito.mock(Logger.class);
}
}
And when I write this test for the same:
#Test
void tesCustomLoggerBeanPresence() {
SpringApplciation application = new SpringApplication(CustomLogger.class);
application.setWebApplicationType(WebApplicationType.NONE);
runner.withBean(CustomLogMockProvider.class)
.run(context -> {
String[] beanNamesForType = context.getBeanNamesForType(Logger.class);
Arrays.stream(beanNamesForType).forEach(System.out::println);
});
}
Getting UnsatisfiedDependencyException for the above one.
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'applicationReadyEventListener': Unsatisfied dependency expressed through constructor parameter 0: Could not convert argument value of type [java.lang.Class] to required type [com.priti.com.common.config.CustomLogger]: Failed to convert value of type 'java.lang.Class' to required type 'com.priti.com.common.config.CustomLogger'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.Class' to required type 'com.priti.com.common.config.CustomLogger': no matching editors or conversion strategy found
Any lead on this would be helpful.
I think your test setup is the problem. You're doing far to much things by hand instead of using the proper Spring tooling.
If you want to test your ApplicationReadyEventListener the test should look like this:
#ExtendWith({SpringExtension.class})
#ContextConfiguration(classes = ApplicationReadyEventListener.class)
class ApplicationReadyEventListenerTest {
#MockBean
private SpringApplication springApplicationMock;
#MockBean
private CustomLogger customLoggerMock;
#Autowired
private ApplicationEventPublisher publisher;
#Test
void name() {
publisher.publishEvent(new ApplicationReadyEvent(springApplicationMock, null, null));
verify(customLoggerMock).doSomething();
}
}
You run a Spring test by using SpringExtension.class. You mock your CustomLogger as well as the SpringApplication. Now you can publish a ApplicationReadyEvent and verify that your listener has invoked the CustomLogger.

Spring Boot : Sharing a bean between different components

I have a bean which I've declared in my bean config as thus:
#Configuration
public class BeanConfig {
#Bean
public MemberDTO getMemberDTO() {
return new MemberDTO();
}
}
When a user calls my service, I use the username and password they've provided to call the endpoint of a different service to get the user's information:
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
private static final Logger LOGGER = LogManager.getLogger(CustomAuthenticationProvider.class);
private #Autowired MemberDTO memberDTO;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String loginGeniuneFailMessage = "";
boolean loginGeniuneFail = false;
try {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
String endPoint = credentialsBaseUrl + "/api/login";
HttpResponse<MemberDTO> response_auth = Unirest.get(endPoint)
.basicAuth(username, password)
.header("Accept", "*/*")
.asObject(MemberDTO.class);
int status_auth = response_auth.getStatus();
if (status_auth == 200) {
if (response_auth.getBody() == null) {
LOGGER.info("account validation - could not parse response body to object");
UnirestParsingException ex = response_auth.getParsingError().get();
LOGGER.error("parsing error: ", ex);
} else {
memberDTO = response_auth.getBody();
}
}
...
} catch (Exception ex) {
...
}
}
I want to store the user's information in the memberDTO and use the memberDTO elsewhere in a different component, rather than calling the login API every time:
#Component
public class MemberLogic {
private #Autowired MemberDTO memberDTO;
public ResponseEntity<?> processMemberInformation(WrapperDTO wrapperDTO, BindingResult result) {
if (result.hasFieldErrors()) {
String errors = result.getFieldErrors().stream()
.map(p -> p.getDefaultMessage()).collect(Collectors.joining("\n"));
return ResponseEntity.badRequest().body("An error occured while trying to persist information: " + errors);
}
String name = memberDTO.getName();
...
}
}
The problem now is the "memberDTO.getName()" is returning null, even though this value is being set from the initial API call in CustomAuthenticationProvider.
My questions are: why isn't this working? And is this the best approach to take for something like this?
Thanks.
My questions are: why isn't this working? And is this the best approach to take for something like this?
This doesn't work because Java uses pass-by-value semantics instead of pass-by-reference semantics. What this means is that the statement memberDTO = response_auth.getBody(); does not really make the Spring container start pointing to the MemberDTO returned by response_auth.getBody(). It only makes the memberDTO reference in CustomAuthenticationProvider point to the object in the response. The Spring container still continues to refer to the original MemberDTO object.
One way to fix this would be to define a DAO class that can be used for interacting with DTO instances rather than directly creating a DTO bean :
#Configuration
public class BeanConfig {
#Bean
public MemberDAO getMemberDAO() {
return new MemberDAO();
}
}
CustomAuthenticationProvider can then set the MemberDTO in the MemberDAO by using : memberDAO.setMemberDTO(response_auth.getBody());
Finally, MemberLogic can access the MemberDTO as String name = memberDAO.getMemberDTO().getName();
Note : Instead of returning the MemberDTO from the MemberDAO, the MemberDAO can define a method called getName which extracts the name from the MemberDTO and returns it. (Tell Don't Ask principle). That said and as suggested in the comments, the best practice would be to use a SecurityContext to store the user information.
The problem is, that you can not override a spring bean "content" like this memberDTO = response_auth.getBody(); because it changes only the instance variable for the given bean. (And its also not good because its out of the spring boot context and it overrides only the field dependency for this singleton bean)
You should not use a normal spring bean for holding data (a state). All the spring beans are singleton by default and you could have some concurrency problems.
For this you should use a database, where you write your data or something like a session bean.

Autowire property in #Component from PropertySourcesPlaceholderConfigurer [duplicate]

Shown below is a snippet of code where I try and reference my ApplicationProperties bean. When I reference it from the constructor it is null, but when referenced from another method it is fine. Up until now I have not had no problem using this autowired bean in other classes. But this is the first time I have tried to use it in the constructor of another class.
In the code snippet below applicationProperties is null when called from the constructor but when referenced in the convert method it is not. What am I missing
#Component
public class DocumentManager implements IDocumentManager {
private Log logger = LogFactory.getLog(this.getClass());
private OfficeManager officeManager = null;
private ConverterService converterService = null;
#Autowired
private IApplicationProperties applicationProperties;
// If I try and use the Autowired applicationProperties bean in the constructor
// it is null ?
public DocumentManager() {
startOOServer();
}
private void startOOServer() {
if (applicationProperties != null) {
if (applicationProperties.getStartOOServer()) {
try {
if (this.officeManager == null) {
this.officeManager = new DefaultOfficeManagerConfiguration()
.buildOfficeManager();
this.officeManager.start();
this.converterService = new ConverterService(this.officeManager);
}
} catch (Throwable e){
logger.error(e);
}
}
}
}
public byte[] convert(byte[] inputData, String sourceExtension, String targetExtension) {
byte[] result = null;
startOOServer();
...
Below is s snippet from ApplicationProperties ...
#Component
public class ApplicationProperties implements IApplicationProperties {
/* Use the appProperties bean defined in WEB-INF/applicationContext.xml
* which in turn uses resources/server.properties
*/
#Resource(name="appProperties")
private Properties appProperties;
public Boolean getStartOOServer() {
String val = appProperties.getProperty("startOOServer", "false");
if( val == null ) return false;
val = val.trim();
return val.equalsIgnoreCase("true") || val.equalsIgnoreCase("on") || val.equalsIgnoreCase("yes");
}
Autowiring (link from Dunes comment) happens after the construction of an object. Therefore they will not be set until after the constructor has completed.
If you need to run some initialization code, you should be able to pull the code in the constructor into a method, and annotate that method with #PostConstruct.
To have dependencies injected at construction time you need to have your constructor marked with the #Autowired annoation like so.
#Autowired
public DocumentManager(IApplicationProperties applicationProperties) {
this.applicationProperties = applicationProperties;
startOOServer();
}
Yes, both answers are correct.
To be honest, this problem is actually similar to the post Why is my Spring #Autowired field null? .
The root cause of the error can be explained in the Spring reference doc (Autowired) , as follow:
Autowired Fields
Fields are injected right after construction of a bean, before any
config methods are invoked.
But the real reason behind this statement in Spring doc is the Lifecycle of Bean in Spring. This is part of Spring's design philosophy.
This is Spring Bean Lifecycle Overview:
Bean needs to be initialized first before it can be injected with properties such as field. This is how beans are designed, so this is the real reason.
I hope this answer is helpful to you!

How can I inject mocks into spring security classes

I am using #PreAuthorize to secure a web request
#PreAuthorize("(#assessmentAuthService.canViewAssessment(#id))")
AssessmentAuthService is created automatically by spring, but I need to inject a mock object into it from my unit test.
private final AssessmentService assessmentService;
#Inject
public AssessmentAuthorizationServiceImpl(AssessmentService assessmentService) {
this.assessmentService = assessmentService;
}
This is because I want assessmentService.getById(assessmentId) to return a mocked object.
I already tried setting it with this but it doesn't work:
ReflectionTestUtils.setField(assessmentAuthorizationService,
"assessmentService", mockAssessmentService);
I was trying to inject a mock into AssessmentAuthorizationService with ReflectionTestUtils.setField but I didn't realize that spring beans are proxies. I had to use this method to unwrap the proxy before I set the field.
class TestUtils {
...
public static Object unwrapService(Object service) throws Exception {
final boolean aopProxy = AopUtils.isAopProxy(service);
final boolean instanceCheck = service instanceof Advised;
if (aopProxy && instanceCheck) {
Object target = ((Advised) service).getTargetSource().getTarget();
return target;
}
return null;
}
}
After I did that it worked. I could set the field to a mock and the define the behavior that I wanted.
ReflectionTestUtils.setField(TestUtil.unwrapService(assessmentAuthorizationService),
"assessmentService", mockAssessmentService);
when(mockAssessmentService.getById(eq(2L))).thenReturn(Optional.of(assessment));

Referring to #Autowired component in another component's constructor [duplicate]

Shown below is a snippet of code where I try and reference my ApplicationProperties bean. When I reference it from the constructor it is null, but when referenced from another method it is fine. Up until now I have not had no problem using this autowired bean in other classes. But this is the first time I have tried to use it in the constructor of another class.
In the code snippet below applicationProperties is null when called from the constructor but when referenced in the convert method it is not. What am I missing
#Component
public class DocumentManager implements IDocumentManager {
private Log logger = LogFactory.getLog(this.getClass());
private OfficeManager officeManager = null;
private ConverterService converterService = null;
#Autowired
private IApplicationProperties applicationProperties;
// If I try and use the Autowired applicationProperties bean in the constructor
// it is null ?
public DocumentManager() {
startOOServer();
}
private void startOOServer() {
if (applicationProperties != null) {
if (applicationProperties.getStartOOServer()) {
try {
if (this.officeManager == null) {
this.officeManager = new DefaultOfficeManagerConfiguration()
.buildOfficeManager();
this.officeManager.start();
this.converterService = new ConverterService(this.officeManager);
}
} catch (Throwable e){
logger.error(e);
}
}
}
}
public byte[] convert(byte[] inputData, String sourceExtension, String targetExtension) {
byte[] result = null;
startOOServer();
...
Below is s snippet from ApplicationProperties ...
#Component
public class ApplicationProperties implements IApplicationProperties {
/* Use the appProperties bean defined in WEB-INF/applicationContext.xml
* which in turn uses resources/server.properties
*/
#Resource(name="appProperties")
private Properties appProperties;
public Boolean getStartOOServer() {
String val = appProperties.getProperty("startOOServer", "false");
if( val == null ) return false;
val = val.trim();
return val.equalsIgnoreCase("true") || val.equalsIgnoreCase("on") || val.equalsIgnoreCase("yes");
}
Autowiring (link from Dunes comment) happens after the construction of an object. Therefore they will not be set until after the constructor has completed.
If you need to run some initialization code, you should be able to pull the code in the constructor into a method, and annotate that method with #PostConstruct.
To have dependencies injected at construction time you need to have your constructor marked with the #Autowired annoation like so.
#Autowired
public DocumentManager(IApplicationProperties applicationProperties) {
this.applicationProperties = applicationProperties;
startOOServer();
}
Yes, both answers are correct.
To be honest, this problem is actually similar to the post Why is my Spring #Autowired field null? .
The root cause of the error can be explained in the Spring reference doc (Autowired) , as follow:
Autowired Fields
Fields are injected right after construction of a bean, before any
config methods are invoked.
But the real reason behind this statement in Spring doc is the Lifecycle of Bean in Spring. This is part of Spring's design philosophy.
This is Spring Bean Lifecycle Overview:
Bean needs to be initialized first before it can be injected with properties such as field. This is how beans are designed, so this is the real reason.
I hope this answer is helpful to you!

Categories

Resources