How to autowire feign client (for external API) in a test? - java

I've written a simple Feign client, for calling an external API (running on a different server):
#FeignClient(
name = "test-service",
url = "https://some-test-server.com/api"
)
public interface TestClient {
#RequestMapping(method = RequestMethod.POST, value = "/test")
TestResponse test(TestRequest request);
}
I wrote some simple bean classes, TestRequest & TestResponse to model the request / response - I'm expecting them to be serialized & deserialized as json.
I want to just test that its able to make the HTTP call and receive a response, so I wrote a test:
#SpringBootTest
#EnableFeignClients(clients = TestClient.class)
class ClientApplicationTests {
#Autowired
private TestClient client;
#Test
void contextLoads() {
System.out.println(client.test(TestRequest.builder().foo("foo").build()));
}
But Intellij warns me that no Beans were found to autowire TestClient, and running this gives a similar exception:
java.lang.NoClassDefFoundError: org/springframework/cloud/context/named/NamedContextFactory$Specification
at java.base/java.lang.ClassLoader.defineClass1(Native Method) ~[na:na]
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1012) ~[na:na]
at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:150) ~[na:na]
at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:862) ~[na:na]
at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:760) ~[na:na]
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:681) ~[na:na]
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:639) ~[na:na]
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) ~[na:na]
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520) ~[na:na]
at org.springframework.cloud.openfeign.FeignClientsRegistrar.registerClientConfiguration(FeignClientsRegistrar.java:410) ~[spring-cloud-openfeign-core-3.1.3.jar:3.1.3]
What am I doing wrong?

Try giving your Feign client class name in this format and check whether this works,
#EnableFeignClients(basePackageClasses=com.abc.xxx.client.TestClient.class)
This parameter accept single or multiple class name. You can also give the base package of the same,
#EnableFeignClients(basePackages = {"my.external.feign.client.package", "my.local.package"})

From the javadoc of #EnableFeignClients:
Scans for interfaces that declare they are feign clients (via FeignClient #FeignClient). Configures component scanning directives for use with org.springframework.context.annotation.Configuration #Configuration classes.
Generally this is placed on a #SpringBootApplication annotated class so that your Feign clients are available in production as well as your #SpringBootTest's.
If you really want to enable your Feign client only in your test, it should look something like this:
#SpringBootTest
class ClientApplicationTests {
#EnableFeignClients(clients = TestClient.class)
#Configuration
protected static class ClientApplicationTestsConfig {
}
#Autowired
private TestClient client;
#Test
void contextLoads() {
System.out.println(client.test(TestRequest.builder().foo("foo").build()));
}
}
This way your test will use the nested #Configuration instead of the automatically found #SpringBootConfiguration/#SpringBootApplication.

Related

Class Cast Exception between Spring Bean and EJB 3 - Jboss7

I have an Spring Controller with a EJB injected. I can inject this EJB using spring proxy:
<jee:local-slsb id="procedimentRepositoryBean"
jndi-name="java:global/sedeib/sedeib-ejb/ProcedimentServiceFacade"
business-interface="es.caib.sedeib.service.facade.ProcedimentServiceFacade"/>
Both uses the same DTO, and when the EJB (procedimentServiceFacade) returns a filled DTO the spring controller try to Cast the DTO to the same DTO. It throws a ClassCastException:
#Controller
#RequestMapping("/prova/")
public class TestController {
#Autowired
private ProcedimentServiceFacade procedimentServiceFacade; //EJB Proxy INJECTION
#RequestMapping(value = "test", method = RequestMethod.GET)
#ResponseBody
public ProcedimentDTO test(){
ProcedimentDTO dto = procedimentServiceFacade.findById(1L).orElse(new ProcedimentDTO());
return dto;
}
}
Stack trace:
Caused by: java.lang.ClassCastException: class es.caib.sedeib.service.model.ProcedimentDTO cannot be cast to class es.caib.sedeib.service.model.ProcedimentDTO (es.caib.sedeib.service.model.ProcedimentDTO is in unnamed module of loader 'deployment.sedeib.ear' #4fd1ef7f; es.caib.sedeib.service.model.ProcedimentDTO is in unnamed module of loader 'deployment.sedeib.ear.sedeib-back.war' #33adbd6e)
at deployment.sedeib.ear.sedeib-back.war//es.caib.sedeib.back.controller.TestController.test(TestController.java:23)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at deployment.sedeib.ear.sedeib-back.war//org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.java:181)
at deployment.sedeib.ear.sedeib-back.war//org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:440)
at deployment.sedeib.ear.sedeib-back.war//org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:428)
at deployment.sedeib.ear.sedeib-back.war//org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967)
at deployment.sedeib.ear.sedeib-back.war//org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
at deployment.sedeib.ear.sedeib-back.war//org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
... 52 more
I read here that it could happen because the spring beans and the EJB are loaded by diferents class loaders. In old Jboss versions, this is solutioned by fixing a class loader with this config:
<class-loading>
<loader-repository>
some-name:loader=name
<loader-repository-config>java2ParentDelegation=false</loader-repository-config>
</loader-repository>
</class-loading>
But it seems deprecated for Jboss7. Are there any way for have the same class loader on both contexts (spring and EJB) ?
Environment:
OpenJdk11
Jboss7.2
EJB3
Spring 4.3.19

Spring boot test without database connection

At first I had the following annotation above my test class:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class)
#AutoConfigureMockMvc
With that configuration it tries to connect to my database, which will give me this error if my database is not running:
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
I would like my test to run without any connection to a database, which is why I tried to change the annotations, so my test class now looks like this:
#RunWith(SpringRunner.class)
#DataJpaTest
#WebMvcTest(CitizenController.class)
#AutoConfigureMockMvc
public class CitizenControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private CitizenRepository citizenRepository;
#MockBean
private WeeklyCareRepository weeklyCareRepository;
#MockBean
private SubCategoryCareRepository subCategoryCareRepository;
#Autowired
private ObjectMapper objectMapper;
private static List<Citizen> mockCitizenList;
private String citizenJson;
However, I am now getting another error:
java.lang.IllegalStateException: Configuration error: found multiple declarations of #BootstrapWith for test class [controllers.CitizenControllerTest]: [#org.springframework.test.context.BootstrapWith(value=class org.springframework.boot.test.context.SpringBootTestContextBootstrapper), #org.springframework.test.context.BootstrapWith(value=class org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTestContextBootstrapper)]
Is it possible to run my test without a database connection? If so, what am I doing wrong/missing?
You can just mock the method that will connect to database in your repository class in the #Test method.
#SpringBootTest
#AutoConfigureMockMvc
class StoreApplicationTests {
#Autowired
private MockMvc mockMvc;
#MockBean
private CitizenRepository citizenRepository;
#Test
void contextLoads() {
}
#Test
public void test() {
Mockito.when(citizenRepository.getDataFromDB()).thenReturn("Something you'd like to Return");
}
}
After doing that, citizenRepository.getDataFromDB() will not connect to database when it's called.
Update After Your Comment:
Then you can just create "src/test/resources" and copy your application.properties or application.yml from "src/main/resources" to that directory and comment the mysql connection part.
If you don't have "src/test/resources/application.properties", then spring will read "src/main/resources/application.properties" by default and configure the project according to that file, since you have datasource configuration in it, spring will try to connect to the database, if database server is down, you would get the failure.

Redis health check for spring boot 2 hangs

I have implemented some redis stuff in my spring boot 2.1.5 application. It works fine.
I also want the health check for redis. If I switch off the redis server the health check (actuator/health) hangs forever.
How can I configure a sensible timeout?
I have created a little demo of this problem here:
https://github.com/markuskruse/demo-redis-health-bug
Clone, run, stop redis, check health (wait forever), start redis (health returns).
This is my gradle for redis:
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
This is my application.yaml:
spring:
redis:
timeout: 5000
host: localhost
This is my RedisConfig.java
#Configuration
#EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig {
#Bean
public LettuceConnectionFactory redisConnectionFactory(
#Value("${spring.redis.host:localhost}") String redisHost) {
RedisStandaloneConfiguration redisStandaloneConfiguration =
new RedisStandaloneConfiguration(redisHost);
return new LettuceConnectionFactory(redisStandaloneConfiguration);
}
#Bean
public StringRedisTemplate redisTemplate(RedisConnectionFactory jedisConnectionFactory) {
final StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(jedisConnectionFactory);
template.afterPropertiesSet();
return template;
}
}
According to this issue on github, it is a mere configuration issue:
https://github.com/spring-projects/spring-boot/issues/15542
According to this jira ticket, it should be fixed in spring boot 2.1.4 (I'm on 2.1.5).
https://jira.spring.io/browse/DATAREDIS-918
They mention a workaround that I have tried:
#Bean
public ClientOptions clientOptions() {
return ClientOptions.builder()
.timeoutOptions(TimeoutOptions.enabled())
.build();
}
By itself, it had no effect. I have to inject it somewhere. Googling gave this:
#Bean
LettucePoolingClientConfiguration lettucePoolConfig(ClientOptions options, ClientResources dcr){
return LettucePoolingClientConfiguration.builder()
.clientOptions(options)
.clientResources(dcr)
.build();
}
Then I get this:
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration]: Factory method 'lettucePoolConfig' threw exception; nested exception is java.lang.NoClassDefFoundError: org/apache/commons/pool2/impl/GenericObjectPoolConfig
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185)
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:622)
... 50 more
Caused by: java.lang.NoClassDefFoundError: org/apache/commons/pool2/impl/GenericObjectPoolConfig
at org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration$LettucePoolingClientConfigurationBuilder.<init>(LettucePoolingClientConfiguration.java:91)
at org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration.builder(LettucePoolingClientConfiguration.java:50)
at com.ikea.cps.mhs.config.RedisConfig.lettucePoolConfig(RedisConfig.java:50)
at com.ikea.cps.mhs.config.RedisConfig$$EnhancerBySpringCGLIB$$3804d114.CGLIB$lettucePoolConfig$3(<generated>)
at com.ikea.cps.mhs.config.RedisConfig$$EnhancerBySpringCGLIB$$3804d114$$FastClassBySpringCGLIB$$ccabed80.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244)
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:363)
at com.ikea.cps.mhs.config.RedisConfig$$EnhancerBySpringCGLIB$$3804d114.lettucePoolConfig(<generated>)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
... 51 more
Caused by: java.lang.ClassNotFoundException: org.apache.commons.pool2.impl.GenericObjectPoolConfig
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:583)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
... 64 more
I can maybe work around this. But I am thinking that I am doing something (fundamentally) wrong. It should already be fixed.
Edit: I added the commons pool and the error goes away, but health check still hangs forever.
I also tried this below, to no effect.
#Component
public class RedisConfigurer implements LettuceClientConfigurationBuilderCustomizer {
#Override
public void customize(LettuceClientConfigurationBuilder builder) {
builder.clientOptions(ClientOptions.builder()
.timeoutOptions(TimeoutOptions.enabled(Duration.of(5, SECONDS))).build());
}
}
It seems that your problem is in your manual Connection factory configuration.
If you remove that part, everything should be fine as you expected.
Otherwise you need to provide a LettuceClientConfiguration for the second argument of the LettuceConnectionFactory constructor and there you can configure ClientOptions with enabled TimeoutOptions

Is it possible to use Spring #Cache* on Feign client?

I have a Feign client that requests a token from a microservice.
Since I am making multiple calls, I would like to cache the responses received from the Feign client and use Spring Guava caching, but it doesn't seem to work. All I have is the configuration class and the annotation on the Feign client.
I read somewhere on stack overflow that Feign might not support #Cacheable annotation. Is this true?
Finally, I managed to solve my issue.
What I did in the end is :
-> Create new #Service annotated class
-> Inject interface with #FeignClient annotation
-> Put all #Cache related annotations ( using #Caching annotation) on the method that calls methods from the interface.
It works! :)
Annotating Feign clients with #Cacheable now works out of the box with Spring Cloud OpenFeign, as per the documentation, since version 3.1.0 (as part of Spring Cloud 2021.0.0).
You only need to make sure that:
you have configured #EnableCaching
you are using spring-cloud-starter-openfeign 3.1.0+ in your dependencies, so this normally means importing spring-cloud-dependencies version 2021.0.0+ (they switched to calver in 2020)
you are using Spring Boot 2.4.1+ (required for this version of Spring Cloud)
What Bianca supposed to do is to add a #Service annotated class to her project where she can use #cacheable annotation.
The traditional way to use FeignClient is to have only an interface annotated with #FeignClient, and then call these methods form other projects/classes. She has added a #Service annotated class, where she call her feignclients methods caching whatever she want.
Traditional:
FeignClient class:
#FeignClient(name="my_feign-client", url = "http://myurl.com/")
public interface MyFeignClient {
#GetMapping("/test")
public ResponseEntity<String> test() throws FeignException;
Class where to call feign client method:
public class TestClass {
#Autowired
private MyFeignClient myFeignClient ;
public String callTest() {
...
return myFeignClient.test();
}
Bianca's method:
Feign client class remains the same.
Service class with cache:
#Service
#CacheConfig(cacheNames={"test"})
public class TestService {
#Autowired
private MyFeignClient myFeignClient ;
#Cacheable
public String callCachedTest() {
...
return myFeignClient.test();
}
And last, the class to call the cached method, that call feignClient:
public class TestClass {
#Autowired
private TestService testService ;
public String callTest() {
...
return testService.callCachedTest();
}

#RefreshScope seems to ignore Mockito's mocks

I'm implementing a service using Spring Boot and Spring Cloud Config service to provide the configuration values. In my Service I have a couple of config values which need to refresh when the value changes in the remote Git repo, and I was using #RefreshScope to enable that feature.
The problem comes when I try to inject a mock for RestTemplate in that service, it appears to ignore it and use the autowired instance instead. If I comment out the annotation it seems to work fine.
Here's the code for the Service:
#Service
#RefreshScope
public class MyServiceImpl implements MyService {
private static final Logger LOG = Logger.getLogger(MyServiceImpl.class);
#Autowired
public RestTemplate restTemplate;
#Value("${opts.default}")
private String default;
#Value("${opts.address}")
private String address;
#Value("${opts.separator}")
private String separator;
...
}
Test source code:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
public class ServiceTest {
#Mock
private RestTemplate restTemplate;
#Autowired
#InjectMocks
private MyServiceImpl service;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
public void testMethod() throws Exception {
when(restTemplate.postForObject(anyString(), any(), eq(ServiceResponse.class), anyMap())).thenReturn(getSuccessfulResponse());
ServiceResponse response = service.doYourStuff();
Assert.assertNotNull(response);
Assert.assertTrue(response.isSuccessful());
}
...
}
When adding the #RefreshScope the bean becomes a proxy instead of an actual raw implementation. Currently the RestTemplate is set on the proxy rather then the underlying instance. (If you debug you would see that your MyServiceImpl is actually more like an instance of MyServiceImpl$SpringCgLib#353234).
To fix you need to manually set the dependency using ReflectionTestUtils and AopTestUtils. The latter is to obtain the actual proxy.
Remove the #InjectMocks annotation and add the following to your setup method after the initialization of the mocks:
Object actualTarget = AopTestUtils.getUltimateTargetObject(service);
ReflectionTestUtils.setfield(actualTarget, "restTemplate", restTemplate);
For versions earlier as 4.2 the following might do the trick
Object actualTarget = (service instanceof Advised) ? ((Advised) service).getTargetSource().getTarget() : service;
The problem is that Mockito doesn't detect the proxy and just sets the field. The ReflectionTestUtils doesn't detect the proxy either hence the manual unwrapping. I actually stepped into this trap a couple of times before, which led me to create SPR-14050 this morning to have it embedded in the ReflectionTestUtils to easy the pain a little.

Categories

Resources