Spring - How to build a junit test for a soap service - spring-boot

I'm following the spring guide to create a hello world soap ws. The link below :
https://spring.io/guides/gs/producing-web-service/
I successfully make it work. When i run this command line :
curl --header "content-type: text/xml" -d
#src/test/resources/request.xml http://localhost:8080/ws/coutries.wsdl
I get this response.
<SOAP-ENV:Header/><SOAP-ENV:Body><ns2:getCountryResponse xmlns:ns2="http://spring.io/guides/gs-producing-web-service"><ns2:country><ns2:name>Spain</ns2:name><ns2:population>46704314</ns2:population><ns2:capital>Madrid</ns2:capital><ns2:currency>EUR</ns2:currency></ns2:country></ns2:getCountryResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>
Now i'm trying to create a junit test for this service (the controller layer) but it doesn't work.
Here is my unit test :
#RunWith(SpringRunner.class)
#WebMvcTest(CountryEndpoint.class)
#ContextConfiguration(classes = {CountryRepository.class, WebServiceConfig.class})
public class CountryEndpointTest {
private final String URI = "http://localhost:8080/ws/countries.wsdl";
#Autowired
private MockMvc mockMvc;
#Test
public void test() throws Exception {
mockMvc.perform(
get(URI)
.accept(MediaType.TEXT_XML)
.contentType(MediaType.TEXT_XML)
.content(request)
)
.andDo(print())
.andExpect(status().isOk());
}
static String request = "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"\n" +
" xmlns:gs=\"http://spring.io/guides/gs-producing-web-service\">\n" +
" <soapenv:Header/>\n" +
" <soapenv:Body>\n" +
" <gs:getCountryRequest>\n" +
" <gs:name>Spain</gs:name>\n" +
" </gs:getCountryRequest>\n" +
" </soapenv:Body>\n" +
"</soapenv:Envelope>";
}
here's the error :
MockHttpServletResponse:
Status = 404
Error message = null
Headers = {}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError: Status
Expected :200
Actual :404
I changed the log level to debug and i found this :
2020-01-27 18:04:11.880 INFO 32723 --- [ main] c.s.t.e.s.endpoint.CountryEndpointTest : Started CountryEndpointTest in 1.295 seconds (JVM running for 1.686)
2020-01-27 18:04:11.925 DEBUG 32723 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Looking up handler method for path /ws/countries.wsdl
2020-01-27 18:04:11.929 DEBUG 32723 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Did not find handler method for [/ws/countries.wsdl]
2020-01-27 18:04:11.930 DEBUG 32723 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Matching patterns for request [/ws/countries.wsdl] are [/**]
2020-01-27 18:04:11.930 DEBUG 32723 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : URI Template variables for request [/ws/countries.wsdl] are {}
2020-01-27 18:04:11.931 DEBUG 32723 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapping [/ws/countries.wsdl] to HandlerExecutionChain with handler [ResourceHttpRequestHandler [locations=[ServletContext resource [/], class path resource [META-INF/resources/], class path resource [resources/], class path resource [static/], class path resource [public/]], resolvers=[org.springframework.web.servlet.resource.PathResourceResolver#c7a977f]]] and 1 interceptor
I tried another solution (below) but it doesn't work either.
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {WebServiceConfig.class, CountryRepository.class})
public class CountryEndpointTest {
private final String URI = "http://localhost:8080/ws/countries.wsdl";
private MockMvc mockMvc;
#Autowired
CountryRepository countryRepository;
#Before
public void setup() {
this.mockMvc = standaloneSetup(new CountryEndpoint(countryRepository)).build();
}

Spring doc says :
https://docs.spring.io/spring-boot/docs/2.1.5.RELEASE/reference/html/boot-features-testing.html
By default, #SpringBootTest will not start a server.
You need to define
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
to RUN the server.
I tried with mockserver, but I can't access to the endpoint (even with WebEnvironment.DEFINED_PORT)
So I did it as follow :
#RunWith(SpringRunner.class)
#ActiveProfiles("test")
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureWebTestClient
public class FacturationEndpointTest {
#Autowired
private WebTestClient webClient;
#Test
public void testWSDL() throws Exception {
this.webClient.get().uri("/test_service/services.wsdl")
.exchange().expectStatus().isOk();
}
You need to add the following dependency in your pom.xml if you want to use WebTestClient like me :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<scope>test</scope>
</dependency>

Please change the GET method to POST.
mockMvc.perform(
postURI) // <-- This line!!!
.accept(MediaType.TEXT_XML)
.contentType(MediaType.TEXT_XML)
.content(request)

if you are using a spring ws framework to implements your endpoints, please see spring-ws-test. you will find a MockWebServiceClient that mocks a client and tests your endpoint. I propose you to see this example : https://memorynotfound.com/spring-ws-server-side-integration-testing/
this works only for spring web service and not for CXF web services.

Related

#SpringBootTest constructor is running multiple times

I am trying to run below test case in Spring Boot.
:: Spring Boot :: (v2.3.1.RELEASE)
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
#SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = com.dineoutsafe.api.dosadmin.DOSAdminAPIApplication.class)
#ActiveProfiles("test")
public class POSTSessionTest {
public POSTSessionTest() {
System.out.println("Calling post construct");
}
#Test
public void testOne(){
assertThat(45,equalTo(30+15));
}
#Test
public void testTwo(){
assertThat(45,equalTo(30+15));
}
#Test
public void testThree(){
assertThat(45,equalTo(30+15));
}
#Test
public void testFour(){
assertThat(45,equalTo(30+15));
}
#Test
public void testFive(){
assertThat(45,equalTo(30+15));
}
}
And I noticed that the constructor is running multiple times. Actually it is running (no. of #Test -1) times.
In standard output
2020-06-21 16:00:26.668 INFO 93912 --- [ task-1] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2020-06-21 16:00:26.679 INFO 93912 --- [ task-1] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2020-06-21 16:00:27.025 INFO 93912 --- [ Test worker] DeferredRepositoryInitializationListener : Spring Data repositories initialized!
2020-06-21 16:00:27.034 INFO 93912 --- [ Test worker] c.d.a.d.i.session.POSTSessionTest : Started POSTSessionTest in 5.511 seconds (JVM running for 6.414)
Calling post construct
Calling post construct
Calling post construct
Calling post construct
Same behaviour I noticed for #PostConstruct.
Is it normal for #SpringBootTest?
This is the default behavior of JUnit5, you can change it by annotating the per-class lifecycle on the class: https://junit.org/junit5/docs/5.0.1/api/org/junit/jupiter/api/TestInstance.Lifecycle.html

Spring Boot RestController: Intercept incoming requests

I am currently writing some kind of Framework that would allow others to write REST Controllers for it. Naturally, I want those "others" to have as little interaction with whats happening in my code as possible.
Specifically, I want and need to access the requests data (i.e.
RequestEntity before the request is handled by the rest controller. Sort of "intercepting" a request before it is handled by the controller, and only then letting the controller handle it.
Consider the following code:
#RestController
#RequestMapping("/")
public class MyController {
#GetMapping("/")
#ResponseBody
public ResponseEntity<String> getSomething(RequestEntity requestEntity) {
MyClass.doStuffWithRequestEntity(requestEntity);
// ...
Now what I would need is that ExternalClass.doStuffWithRequestEntity(requestEntity); is called without the need to explicitly call it. Is it possible to have some method in some class being called (with the RequestEntity passed to it!) without having to call it explicitly?
Furthermore, said Interceptor class should create and configure an object that is then again made available to the rest controller.
I'd be thinking something like
class RestController {
#RestController
#RequestMapping("/")
public class MyController {
#GetMapping("/")
#ResponseBody
public ResponseEntity<String> getSomething() {
MyClass x = MyClass.getInstanceCreatedByInterceptor();
}
}
}
and
class Interceptor {
public void doStuffWithRequestEntity(requestEntity) {
MyClass x = new MyClass();
x.fillObjectWithData();
}
}
being executed before.
The idea is that EVERY (!) incoming request is parsed and its contents get decoded without the programmer of the rest controller getting having to care about this at all. They should just access data via/from the MyClass instance.
Is there a way to do this?
Spring-boot allows us to configure custom interceptors.Usually in a spring boot application everything is auto configured and in such cases we can customize it by using the WebMvcConfigurerAdapter.Just extend WebMvcConfigurerAdapter and provide the configurations that you need in this class.
Remember to add #Configuration annotation so that this class will be picked up by spring during component scan.
#Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Autowired
HandlerInterceptor customInjectedInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(...)
...
registry.addInterceptor(customInjectedInterceptor).addPathPatterns("/**");
}
}
This is how you normally add interceptors to spring boot applications.Hope this might help answer your question.
From spring 5.x.x or spring-boot 2 onwards , WebMvcConfigurerAdapter is marked as deprecated.The WebMvcConfigurer interface (which is implemented by the abstract class WebMvcConfigurerAdapter), starting with Spring 5, contains default implementations for all its methods. As a result, the abstract adapter class was marked as deprecated.You can adopt it if you like as follows :
#Configuration
public WebConfig implements WebMvcConfigurer {
// ...
}
For all those who end up here with the same or a similar question, here is a working minimal example for a SpringBoot (2.1.4) application with an Interceptor:
Minimal.java:
#SpringBootApplication
public class Minimal
{
public static void main(String[] args)
{
SpringApplication.run(Minimal.class, args);
}
}
MinimalController.java:
#RestController
#RequestMapping("/")
public class Controller
{
#GetMapping("/")
#ResponseBody
public ResponseEntity<String> getMinimal()
{
System.out.println("MINIMAL: GETMINIMAL()");
return new ResponseEntity<String>("returnstring", HttpStatus.OK);
}
}
Config.java:
#Configuration
public class Config implements WebMvcConfigurer
{
//#Autowired
//MinimalInterceptor minimalInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry)
{
registry.addInterceptor(new MinimalInterceptor());
}
}
MinimalInterceptor.java:
public class MinimalInterceptor extends HandlerInterceptorAdapter
{
#Override
public boolean preHandle(HttpServletRequest requestServlet, HttpServletResponse responseServlet, Object handler) throws Exception
{
System.out.println("MINIMAL: INTERCEPTOR PREHANDLE CALLED");
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception
{
System.out.println("MINIMAL: INTERCEPTOR POSTHANDLE CALLED");
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) throws Exception
{
System.out.println("MINIMAL: INTERCEPTOR AFTERCOMPLETION CALLED");
}
}
works as advertised
The output will give you something like:
> Task :Minimal.main()
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.4.RELEASE)
2019-04-29 11:53:47.560 INFO 4593 --- [ main] io.minimal.Minimal : Starting Minimal on y with PID 4593 (/x/y/z/spring-minimal/build/classes/java/main started by x in /x/y/z/spring-minimal)
2019-04-29 11:53:47.563 INFO 4593 --- [ main] io.minimal.Minimal : No active profile set, falling back to default profiles: default
2019-04-29 11:53:48.745 INFO 4593 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2019-04-29 11:53:48.780 INFO 4593 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2019-04-29 11:53:48.781 INFO 4593 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.17]
2019-04-29 11:53:48.892 INFO 4593 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2019-04-29 11:53:48.893 INFO 4593 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1269 ms
2019-04-29 11:53:49.130 INFO 4593 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2019-04-29 11:53:49.375 INFO 4593 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2019-04-29 11:53:49.380 INFO 4593 --- [ main] io.minimal.Minimal : Started Minimal in 2.525 seconds (JVM running for 2.9)
2019-04-29 11:54:01.267 INFO 4593 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-04-29 11:54:01.267 INFO 4593 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2019-04-29 11:54:01.286 INFO 4593 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 19 ms
MINIMAL: INTERCEPTOR PREHANDLE CALLED
MINIMAL: GETMINIMAL()
MINIMAL: INTERCEPTOR POSTHANDLE CALLED
MINIMAL: INTERCEPTOR AFTERCOMPLETION CALLED
import javax.servlet.http.HttpServletRequest ;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
#Configuration
public class AuthenticationHandlerInterceptor implements HandlerInterceptor {
#Override
public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
throws Exception {
}
#Override
public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
throws Exception {
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object arg2) throws Exception {
if (validrequest) {
//fill data here add pass to next level
return true;
} else {
// if you opt to not to proceed the request further you can simply return false here
return false;
}
}
}
prehandle() – called before the actual handler is executed, but the view is not generated yet
postHandle() – called after the handler is executed
afterCompletion() – called after the complete request has finished and view was generated

#Cacheable and initialization during startup

I would like to initialize all entries in cache during startup of my spring boot application (loading stuff from DB). Ideally, this is done before the application is already ready. So I implemented all loading in #PostConstruct. I remarked, that the cache is not already setup in #PostContruct and I followed some tips to do such initializations in the ApplicationReadyEvent. However, this still does not work as expected:
Even though I already call a #Cacheable Method in ApplicationReadyEvent, the second invocation re-enters the method instead of using the cache.
My Service:
#Service
public class MyService implements ApplicationListener<ApplicationReadyEvent {
#Cacheable("entry")
public List<String> getEntry() {
System.out.println("getEntry called!");
return Arrays.asList("aaa", "bbb");
}
#Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
System.out.println("*** onApplicationEvent");
getEntry();
}
}
My Caffeine CacheManager Config:
#Configuration
#EnableCaching
public class CachingConfig {
#Bean
public CacheManager cacheManager() {
List<CaffeineCache> caffeineCaches = chacheList(Arrays.asList(
"entry"
));
SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
simpleCacheManager.setCaches(caffeineCaches);
System.out.println("*** #Bean CacheManager");
return simpleCacheManager;
}
private List<CaffeineCache> chacheList(List<String> cacheNames) {
return cacheNames.stream().map(s -> new CaffeineCache(s, Caffeine.newBuilder().build()))
.collect(Collectors.toList());
}
}
A simple REST endpoint using the service:
#RestController
public class MyController {
#Autowired
MyService myService;
#GetMapping("/test")
public void test()
{
System.out.println("*** GET /test");
myService.getEntry();
}
}
If I start the application and perform two GET /test, I get the following output:
INFO 20120 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 907 ms
*** #Bean CacheManager
INFO 20120 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
INFO 20120 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
INFO 20120 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 1.639 seconds (JVM running for 2.473)
*** onApplicationEvent
*** getEntry called!
INFO 20120 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
INFO 20120 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
INFO 20120 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 4 ms
*** GET /test
*** getEntry called!
*** GET /test
So why does the second invocation of MyService.getEntry (i.e. the first invocation after "Startup") enters the code again?
At the end, I need a solution, which performs the first loading before the application finished to startup - i.e. I will try ContextRefreshedEvent or again #PostConstruct (and #Autowire CacheManager to have it configured before executing #PostConstruct). But the first step would be to get this example here behave as expected.
Ok, stupid error: in my service, the call to getEntry() must be done over proxy object rather than directly:
#Service
public class MyService implements ApplicationListener<ApplicationReadyEvent {
#Autowired
MyService self;
#Cacheable("entry")
public List<String> getEntry() {
System.out.println("getEntry called!");
return Arrays.asList("aaa", "bbb");
}
#Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
System.out.println("*** onApplicationEvent");
self.getEntry();
}
}

What is order of evaluation for #Autowiring in SpringBoot project

I'm trying to write a simple SprintBoot REST Controller to run on Websphere Liberty, and having a problem with #Autowire. Here are the relevant (I think) pieces of code:
#CrossOrigin
#RestController
#RequestMapping(path = "userSetting")
public class RESTInterface {
#Autowired
private UserSettingDAO userSettingDAO;
#RequestMapping(path = "/getUserSetting", method = { RequestMethod.GET }, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public String getUserSetting(
#RequestParam(value="user_id", required=true ) String user_id,
#RequestParam(value="app_id", required=true ) String app_id,
#RequestParam(value="dashboard_name", required=true ) String dashboard_name
) {
if ( userSettingDAO == null ) {
System.out.println( "userSettingDAO is null" );
...
////////////////////////////////////////////////////////////////////////////////
#Component
public class UserSettingDAO {
#Autowired
private JdbcTemplate jdbcTemplate;
#Autowired
private DataSource dataSource;
public UserSettingDAO() {
System.out.println( "dataSource is " + dataSource );
System.out.println( "jdbcTemplate is " + jdbcTemplate );
When I deploy the application to Liberty, it appears to start up OK.
When I hit the appropriate endpoint, I see the following in console.log:
UserSettingDAO.jdbcTemplate = null
dataSource is null
jdbcTemplate is null
2018-04-24 16:54:19.887 INFO 8807 --- [cutor-thread-11] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/userSetting/getUserSetting],methods=[GET],produces=[application/json;charset=UTF-8]}" onto public java.lang.String com.ui.usersetting.restinterface.RESTInterface.getUserSetting(java.lang.String,java.lang.String,java.lang.String)
2018-04-24 16:54:19.890 INFO 8807 --- [cutor-thread-11] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/userSetting/upsertUserSetting],methods=[POST],produces=[application/json;charset=UTF-8]}" onto public void com.ui.usersetting.restinterface.RESTInterface.upsertUserSetting(java.lang.String,java.lang.String,java.lang.String,java.lang.String)
2018-04-24 16:54:19.891 INFO 8807 --- [cutor-thread-11] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/userSetting/deleteUserSetting],methods=[DELETE],produces=[application/json;charset=UTF-8]}" onto public void com.ui.usersetting.restinterface.RESTInterface.deleteUserSetting(java.lang.String,java.lang.String,java.lang.String)
2018-04-24 16:54:19.893 INFO 8807 --- [cutor-thread-11] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/userSetting/hello],methods=[GET],produces=[application/json;charset=UTF-8]}" onto public java.lang.String com.ui.usersetting.restinterface.RESTInterface.hello()
2018-04-24 16:54:19.924 INFO 8807 --- [cutor-thread-11] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2018-04-24 16:54:19.925 INFO 8807 --- [cutor-thread-11] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2018-04-24 16:54:20.166 INFO 8807 --- [cutor-thread-11] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for #ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext#6a5997f7: startup date [Tue Apr 24 16:54:15 EDT 2018]; root of context hierarchy
2018-04-24 16:54:21.386 INFO 8807 --- [cutor-thread-11] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2018-04-24 16:54:21.389 INFO 8807 --- [cutor-thread-11] o.s.j.e.a.AnnotationMBeanExporter : Bean with name 'dataSource' has been autodetected for JMX exposure
2018-04-24 16:54:21.396 INFO 8807 --- [cutor-thread-11] o.s.j.e.a.AnnotationMBeanExporter : Located MBean 'dataSource': registering with JMX server as MBean [com.zaxxer.hikari:name=dataSource,type=HikariDataSource]
My question is: Why are the two println statements saying that their respective variables are null? Does it have something to do with the fact that the UserSettingDAO constructor appears in the log to be executed before the lines about dataSource appear in the log?
What should I do to get those variables properly initialized?
At the time a Spring component's constructor is called, any #Autowired objects will still be null. This is because the spring container needs to initialize an instance of the class before it can inject values into the #Autowired fields.
I don't know how Spring implements injection, but most Java injection works something like this:
Construct new instance of the bean class, normally by invoking default ctor (or initializing a dynamically generated sub-class proxy)
Obtain objects that need to be injected into the bean
Using reflection/proxy, set the fields on the bean
To validate this, I created a method on UserSettingsDAO that utilizes the #AutoWired fields:
#Component
public class UserSettingDAO {
#Autowired
private JdbcTemplate jdbcTemplate;
#Autowired
private DataSource ds;
public UserSettingDAO() {
System.out.println("ctor/jdbcTemplate is " + jdbcTemplate );
System.out.println("ctor/datasource is: " + ds);
}
public void doStuff() {
System.out.println("doStuff/jdbcTemplate is " + jdbcTemplate );
System.out.println("doStuff/datasource is: " + ds);
}
}
If we inject this DAO into another class and use it, we will see that the fields are initialized after the UserSettingDAO is constructed:
#CrossOrigin
#RestController
public class RESTInterface {
#Autowired
private UserSettingDAO dao;
#RequestMapping(path = "/jdbc", method = { RequestMethod.GET }, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public String jdbc() {
return dao.doStuff();
}
}
Checking logs, this will produce the following output:
ctor/jdbcTemplate is null
ctor/datasource is: null
doStuff/jdbcTemplate is org.springframework.jdbc.core.JdbcTemplate#4eb46bed
doStuff/datasource is: com.ibm.ws.rsadapter.jdbc.WSJdbcDataSource#727d23c5

Testing Spring Boot Eureka Server 404

I'm trying to test authentication in my Spring Boot Eureka Server. To do so, I perform a GET on /eureka/apps. I get a 404 instead of 200.
#RunWith(SpringRunner.class)
#WebAppConfiguration
#SpringBootTest(classes = Application.class)
public class GlobalSecurityTest {
#Autowired
private WebApplicationContext wac;
#Autowired
private FilterChainProxy springSecurityFilterChain;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
.addFilter(springSecurityFilterChain).build();
}
#Test
public void givenRoleDiscoveryClient_whenGetEureka_then200() throws Exception {
mockMvc.perform(get("/eureka/apps").header(HttpHeaders.AUTHORIZATION, TOKEN_DISCOVERY_CLIENT)
.andExpect(status().isOk());
}
}
Eureka starts correctly as the logs prove:
2018-04-12 23:07:39.308 INFO 80833 --- [ Thread-12] e.s.EurekaServerInitializerConfiguration : Started Eureka Server
2018-04-12 23:07:39.315 INFO 80833 --- [ main] GlobalSecurityTest : Started GlobalSecurityTest in 7.255 seconds (JVM running for 8.007)
...
2018-04-12 23:07:39.822 DEBUG 80833 --- [ main] o.s.security.web.FilterChainProxy : /eureka/apps/REGISTRY reached end of additional filter chain; proceeding with original chain
2018-04-12 23:07:39.831 DEBUG 80833 --- [ main] w.c.HttpSessionSecurityContextRepository : SecurityContext 'org.springframework.security.core.context.SecurityContextImpl#0: Authentication: StateTokenAuthentication{principalTokenState=be.charliebravo.ibpt.qos3.commons.security.models.ClientState#50b624da, tokenStates={}}' stored to HttpSession: 'org.springframework.mock.web.MockHttpSession#50b4e7b2
2018-04-12 23:07:39.833 DEBUG 80833 --- [ main] o.s.s.w.a.ExceptionTranslationFilter : Chain processed normally
2018-04-12 23:07:39.833 DEBUG 80833 --- [ main] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
java.lang.AssertionError: Status
Expected :200
Actual :404
My security config:
#Configuration
public class WebSecurityConfig {
#Configuration
#Order(3)
public static class DiscoveryClientSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private StateTokenHttpSecurityConfigurer stateTokenHttpSecurityConfigurer;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/eureka/**").authorizeRequests()
.anyRequest().hasRole(Role.DISCOVERY_CLIENT.toString())
.and().exceptionHandling().authenticationEntryPoint(new Http401UnauthorizedEntryPoint());
stateTokenHttpSecurityConfigurer.configure(http);
}
}
}
The Eureka server works fine when I run the application instead of the test.
Don't use MockMvc, because it is limited to testing the web layer, but Eureka mappings aren't registered there. Instead, use TestRestTemplate.
Remove #WebAppConfiguration and add weEnvironment setting in #SpringBootTest
#SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
Autowire TestRestTemplate and local server port
#Autowired
private TestRestTemplate restTemplate;
#LocalServerPort
private int localServerPort;
Perform the request
#Test
public void givenRoleDiscoveryClient_whenGetEurekaPage_then200() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.AUTHORIZATION, TOKEN_DISCOVERY_CLIENT);
HttpEntity entity = new HttpEntity<>(null, headers);
String endpoint = "https://localhost:" + localServerPort + "/eureka/apps";
ResponseEntity responseEntity = restTemplate.exchange(endpoint, HttpMethod.GET, entity, String.class);
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
And off you go.

Resources