Testing Hystrix fallback through Feign API: com.netflix.client.ClientException: Load balancer does not have available server for client - spring-boot

When testing the Hystrix fallback behavior of my Feign API, I get an error, when I expect it to succeed.
Feign interface:
This is the api to the external service.
#FeignClient(name = "book", fallback = BookAPI.BookAPIFallback.class)
public interface BookAPI {
#RequestMapping("/")
Map<String, String> getBook();
#Component
class BookAPIFallback implements BookAPI {
#Override
#RequestMapping("/")
public Map<String, String> getBook() {
Map<String, String> fallbackmap = new HashMap<>();
fallbackmap.put("book", "fallback book");
return fallbackmap;
}
}
}
Test class
This test exists just to verify fallback behavior:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = NONE)
public class BookServiceClientTest {
#MockBean
RestTemplate restTemplate;// <---- #LoadBalanced bean
#Autowired
private BookServiceClient bookServiceClient;
#Before
public void setup() {
when(restTemplate.getForObject(anyString(), any()))
.thenThrow(new RuntimeException("created a mock failure"));
}
#Test
public void fallbackTest() {
assertThat(bookServiceClient.getBook())
.isEqualTo(new BookAPI.BookAPIFallback().getBook().get("book")); // <--- I thought this should work
}
}
config files
application.yml
These files show configuration that might be relevant:
feign:
hystrix:
enabled: true
test/application.yml
eureka:
client:
enabled: false
The Question
Everything works fine when running the apps.
But when running this test, I get the below error.
Naturally, it's a test, so I'm trying to bypass the lookup anyway.
java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: book
at org.springframework.cloud.netflix.feign.ribbon.LoadBalancerFeignClient.execute(LoadBalancerFeignClient.java:71)
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:97)
What am I missing?
Addendums
Application class
#SpringBootApplication
#EnableCircuitBreaker
#EnableDiscoveryClient
#EnableFeignClients
public class LibraryApplication {
public static void main(String[] args) {
SpringApplication.run(LibraryApplication.class, args);
}
}
LibraryController
#Controller
public class LibraryController {
private final BookServiceClient bookService;
public LibraryController(BookServiceClient bookServiceClient) {
this.bookService = bookServiceClient;
}
#GetMapping("/")
String getLibrary(Model model) {
model.addAttribute("msg", "Welcome to the Library");
model.addAttribute("book", bookService.getBook());
return "library";
}
}
There are no other classes.

so! I was able to recreate the issue, thanks for adding more code, had to play about with it a tad as I was unsure what the BookClientService looked like and it wouldn't make sense for it to implement the BookAPI as that would be an internal call e.g. in your application and not an external API call with Feign.
Anyway,
I pushed my version of what you provided here.
https://github.com/Flaw101/feign-testing
The issue was resolved when I renamed the second application.yml which lives in the src/test/resources folder to application-test.yml which will merge the properties.
The issue was caused by the fact the second property source, the testing one, overrides the initial application.yml and disables hystrix, because Hystrix is disabled there is no fallback to go to and it throws the root cause of what would cause the fallback, a lack of a server to call to for the Book API. Renaming it to application-test will always be loaded into spring test contexts. You could resolve it with the use of inlined properties or profiles.
I've added another test disabling feign /w hystrix within the test which re-creates the error you are recieving.

Related

Spring Boot Injecting Implementations for Prod and Test

I'm new to spring boot and I'm trying to wrap my head around how to make dependency injection work for deployment and testing.
I have a #RestController and a supporting #Service. The service injects another class that is an interface for talking to Kafka. For the Kafka interface I have two implementations: one real and one fake. The real one I want to use in production and the fake in test.
My approach is to use two different configuration for each environment (prod and test).
#Configuration
public class AppTestConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherFakeImpl();
}
}
#Configuration
public class AppConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherImpl();
}
}
Then in my main application I would like to somehow load AppConfiguration.
#SpringBootApplication
public class DeployerServiceApiApplication {
public static void main(String[] args) {
SpringApplication.run(DeployerServiceApiApplication.class, args);
}
// TODO: somehow load here...
}
And in my test load the fake configuration somehow
#SpringBootTest
#AutoConfigureMockMvc(addFilters = false)
public class DeployerServiceApiApplicationTest {
#Autowired private MockMvc mockMvc;
// TODO: somehow load AppTestConfiguration here
#Test
public void testDeployAction() throws Exception {
...
ResultActions resultActions = mockMvc.perform(...);
...
}
}
I've spent the better part of a day trying to figure this out. What I'm trying to accomplish here is fundamental and should be straight forward yet I keep running into issues which makes me wonder if the way I'm thinking about this is all wrong.
Am not sure if i understand your question completely but from description i guess you wish to initialize bean based on environment. Please see below.
#Profile("test")
#Configuration
public class AppTestConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherFakeImpl();
}
}
#Profile("prod")
#Configuration
public class AppConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherImpl();
}
and then you can pass the "-Dspring.profiles.active=prod" argument while starting you application using java command or you can also specify the profile in your test case like below.
#SpringBootTest
#ActiveProfile("test")
#AutoConfigureMockMvc(addFilters = false)
public class DeployerServiceApiApplicationTest
Use spring profiles, you can annotate your test class with #ActiveProfiles("test-kafka") and your test configuration with #Profile("test-kafka").
This is pretty easy task in spring boot world
Rewrite your classes as follows:
#Profile("test")
#Configuration
public class AppTestConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherFakeImpl();
}
}
#Profile("prod")
#Configuration
public class AppConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherImpl();
}
}
This will instruct spring boot to load the relevant configuration when the "prod"/"test" specified.
Then you can start your application in production with --spring.profiles.active=prod and in the Test you can write something like this:
#SpringBootTest
#ActiveProfiles("test")
public class DeployerServiceApiApplicationTest {
...
}
If you want to run all the tests with this profile and do not want to write this ActiveProfiles annotation you can create src/test/resources/application.properties and put into it: spring.active.profiles=test

Passing an external property to JUnit's extension class

My Spring Boot project uses JUnit 5. I'd like to setup an integration test which requires a local SMTP server to be started, so I implemented a custom extension:
public class SmtpServerExtension implements BeforeAllCallback, AfterAllCallback {
private GreenMail smtpServer;
private final int port;
public SmtpServerExtension(int port) {
this.port = port;
}
#Override
public void beforeAll(ExtensionContext extensionContext) {
smtpServer = new GreenMail(new ServerSetup(port, null, "smtp")).withConfiguration(GreenMailConfiguration.aConfig().withDisabledAuthentication());
smtpServer.start();
}
#Override
public void afterAll(ExtensionContext extensionContext) {
smtpServer.stop();
}
}
Because I need to configure the server's port I register the extension in the test class like this:
#SpringBootTest
#AutoConfigureMockMvc
#ExtendWith(SpringExtension.class)
#ActiveProfiles("test")
public class EmailControllerIT {
#Autowired
private MockMvc mockMvc;
#Autowired
private ObjectMapper objectMapper;
#Value("${spring.mail.port}")
private int smtpPort;
#RegisterExtension
// How can I use the smtpPort annotated with #Value?
static SmtpServerExtension smtpServerExtension = new SmtpServerExtension(2525);
private static final String RESOURCE_PATH = "/mail";
#Test
public void whenValidInput_thenReturns200() throws Exception {
mockMvc.perform(post(RESOURCE_PATH)
.contentType(APPLICATION_JSON)
.content("some content")
).andExpect(status().isOk());
}
}
While this is basically working: How can I use the smtpPort annotated with #Value (which is read from the test profile)?
Update 1
Following your proposal I created a custom TestExecutionListener.
public class CustomTestExecutionListener implements TestExecutionListener {
#Value("${spring.mail.port}")
private int smtpPort;
private GreenMail smtpServer;
#Override
public void beforeTestClass(TestContext testContext) {
smtpServer = new GreenMail(new ServerSetup(smtpPort, null, "smtp")).withConfiguration(GreenMailConfiguration.aConfig().withDisabledAuthentication());
smtpServer.start();
};
#Override
public void afterTestClass(TestContext testContext) {
smtpServer.stop();
}
}
The listener is registered like this:
#TestExecutionListeners(value = CustomTestExecutionListener.class, mergeMode = MERGE_WITH_DEFAULTS)
When running the test the listener gets called but smtpPort is always 0, so it seems as if the #Value annotation is not picked up.
I don't think you should work with Extensions here, or in general, any "raw-level" JUnit stuff (like lifecycle methods), because you won't be able to access the application context from them, won't be able to execute any custom logic on beans and so forth.
Instead, take a look at Spring's test execution listeners abstraction
With this approach, GreenMail will become a bean managed by spring (probably in a special configuration that will be loaded only in tests) but since it becomes a bean it will be able to load the property values and use #Value annotation.
In the test execution listener you'll start the server before the test and stop after the test (or the whole test class if you need that - it has "hooks" for that).
One side note, make sure you mergeMode = MergeMode.MERGE_WITH_DEFAULTS as a parameter to #TestExecutionListeners annotation, otherwise some default behaviour (like autowiring in tests, dirty context if you have it, etc) won't work.
Update 1
Following Update 1 in the question. This won't work because the listener itself is not a spring bean, hence you can't autowire or use #Value annotation in the listener itself.
You can try to follow this SO thread that might be helpful, however originally I meant something different:
Make a GreenMail a bean by itself:
#Configuration
// since you're using #SpringBootTest annotation - it will load properties from src/test/reources/application.properties so you can put spring.mail.port=1234 there
public class MyTestMailConfig {
#Bean
public GreenMail greenMail(#Value(${"spring.mail.port"} int port) {
return new GreenMail(port, ...);
}
}
Now this configuration can be placed in src/test/java/<sub-package-of-main-app>/ so that in production it won't be loaded at all
Now the test execution listener could be used only for running starting / stopping the GreenMail server (as I understood you want to start it before the test and stop after the test, otherwise you don't need these listeners at all :) )
public class CustomTestExecutionListener implements TestExecutionListener {
#Override
public void beforeTestClass(TestContext testContext) {
GreenMail mailServer =
testContext.getApplicationContext().getBean(GreenMail.class);
mailServer.start();
}
#Override
public void afterTestClass(TestContext testContext) {
GreenMail mailServer =
testContext.getApplicationContext().getBean(GreenMail.class);
mailServer.stop();
}
}
Another option is autowiring the GreenMail bean and using #BeforeEach and #AfterEach methods of JUnit, but in this case you'll have to duplicate this logic in different Test classes that require this behavour. Listeners allow reusing the code.

spring boot tests and embedded elastic server

The documentation says that writing #SpringbootTest doesnt mean we load all the configuration
We should be able to test a slice of application at a time
I have a case where one module talks to elastic search and we spin up EmbeddedElasticsearchServer in integration tests
However I have hard time figuring out how to define #Configuration classes, how to load them in test and how to make sure that elasticsearch server spins up once for all the tests
I am not sure about the #SpringBootTest. But you can use the below template for elasticsearch Integration est with embeddedserver
v7.0.0
#ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.SUITE, numDataNodes = 1, numClientNodes = 0, transportClientRatio = 0, supportsDedicatedMasters = false)
#RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class)
public class TestWatsonBulkIT extends ESIntegTestCase {
#Override
protected boolean addMockHttpTransport() {
return false;
}
#Override
protected Settings nodeSettings(int nodeOrdinal) {
Settings.Builder builder = Settings.builder()
.put(super.nodeSettings(nodeOrdinal))
.put(NetworkModule.TRANSPORT_TYPE_KEY, Netty4Plugin.NETTY_HTTP_TRANSPORT_NAME)
.put(NetworkModule.HTTP_TYPE_KEY, Netty4Plugin.NETTY_HTTP_TRANSPORT_NAME)
.put(HttpTransportSettings.SETTING_HTTP_PORT.getKey(), 9200)
.put(HttpTransportSettings.SETTING_HTTP_HOST.getKey(),"127.0.0.1");
Settings settings = builder.build();
return settings;
}
#Before
public void setUp() throws Exception {
beforeClass();
super.setUp();
}
#Test
public void test_1(){ // your integration test code here }
}
We should be able to test a slice of application at a time
This is possible by providing classes in #SpringBootTest annotation. I usually go for this option in order to not load everything. Let's say,
Controller:
SampleController -> ConsumerService (autowired)
Test
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {ConsumerService.class, SampleController.class})
public class TestSample {
#Autowired
SampleController sam;
#Test
public void testSam() {
sam.sample();
}
}
I do have bunch of services but they won't be loaded and so their dependencies autowired.
If this is not what you're looking for please enhance your question by adding some sample codes.

Fallback method is not being called when rest call is failed by using feign client

I am trying to implement fallback by using Feign client but not getting success.Its a simplest code Please find below.
Main Class
#SpringBootApplication
#EnableDiscoveryClient
#RestController
#EnableFeignClients
public class EurekaClient1Application {
#Autowired
public DiscoveryClient discoveryClient;
public static void main(String[] args) {
SpringApplication.run(EurekaClient1Application.class, args);
}
#Autowired
FeingInterface feingInterface;
#GetMapping("/hi/{name}")
public String test(#PathVariable String name)
{
String h = feingInterface.test(name);
return h;
}
}
Feign interface
#FeignClient(name="client22",fallback=FallBack.class)
public interface FeingInterface {
#GetMapping("/hiname/{name}")
public String test(#PathVariable("name") String name);
}
fallback class
#Component
class FallBack implements FeingInterface{
#Override
public String test(String name) {
// TODO Auto-generated method stub
return "fall back methord being called";
}
}
Getting Error in rest client but not from fallback method
"timestamp": 1501950134118,
"status": 500,
"error": "Internal Server Error",
"exception": "java.lang.RuntimeException",
"message": "com.netflix.client.ClientException: Load balancer does not have available server for client: client22",
To get the fallback method message I passed client22 eureka id which is not there in eureka server. I have stater-feign in pom. Can someone look into this.
Fallbacks are actually not handled by Feign itself, but by a circuit breaker. So, you need to put Hystrix (which is the Netflix circuit breaker) on your classpath and enable it in your application.yml file like this:
feign:
hystrix:
enabled: true
If you're using 'cloud:spring-cloud-starter-openfeign' in your build.gradle or pom.xml file, Hystrix should be automatically on your classpath.

Apache Camel Spring Javaconfig Unit Test No consumers available on endpoint

I have the following route configuration:
#Component
public class MyRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
from("direct:in").to("direct:out");
}
}
When I try to test it:
#RunWith(CamelSpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { MyRouteTest.TestConfig.class }, loader = CamelSpringDelegatingTestContextLoader.class)
#MockEndpoints
public class MyRouteTest {
#EndpointInject(uri = "mock:direct:out")
private MockEndpoint mockEndpoint;
#Produce(uri = "direct:in")
private ProducerTemplate producerTemplate;
#Configuration
public static class TestConfig extends SingleRouteCamelConfiguration {
#Bean
#Override
public RouteBuilder route() {
return new MyRoute();
}
}
#Test
public void testRoute() throws Exception {
mockEndpoint.expectedBodiesReceived("Test Message");
producerTemplate.sendBody("Test Message");
mockEndpoint.assertIsSatisfied();
}
}
I get this exception:
org.apache.camel.component.direct.DirectConsumerNotAvailableException:
No consumers available on endpoint: Endpoint[direct://out].
Exchange[Message: Test Message]
It looks like the Mock is not picking up the message from the endpoint.
What am I doing wrong?
The problem is that mock endpoints just intercept the message before delegating to the actual endpoint. Quoted from the docs:
Important: The endpoints are still in action. What happens differently
is that a Mock endpoint is injected and receives the message first and
then delegates the message to the target endpoint. You can view this
as a kind of intercept and delegate or endpoint listener.
The solution to your problem is to tell certain endpoints (the ones that expect a consumer in your case) not to delegate to the actual endpoint. This can easily be done using #MockEndpointsAndSkip instead of #MockEndpoints:
#RunWith(CamelSpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { MyRouteTest.TestConfig.class }, loader = CamelSpringDelegatingTestContextLoader.class)
#MockEndpointsAndSkip("direct:out") // <-- turns unit test from red to green ;)
public class MyRouteTest {
// ....
}
This issue because, in your route configuration, there is no route with "direct:out" consumer endpoint.
add a line like some thing below,
from("direct:out").("Anything you want to log");
So that direct:out will consume the exchange and In your test, mock will be able check the received text without any issues. Hope this helps !!

Resources