#WebFluxTest throws org.springframework.boot.context.properties.bind.Binder has not been registered error on Jenkins pipeline - spring

I've built a Spring WebFlux application (Annotated Controller model) that integrates with Google Cloud Services with the below mentioned library versions. I've written a test for Controller class and when it is run on my local machine it runs without any issue. But when the same test is run on Jenkins pipeline it throws Binder has not been registered error.
Spring Boot Version - 2.5.5
Spring Cloud GCP Version - 2.0.4
The error that gets printed on the Jenkins console is as below
[ERROR] com.test.controller.ControllerTest.shouldPostRequestToEndpoint Time elapsed: 0.001 s <<< ERROR!
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: java.lang.IllegalStateException: org.springframework.boot.context.properties.bind.Binder has not been registered
The most confusing thing is this issue happens only on Jenkins instance and not on my local machine and it makes it very difficult to debug. I'm not sure on why it appears that way. Also, not sure how to register any Binder here. If I could get any help/direction on where the issue might be or how to debug, it would be very helpful.
Sample Controller and ControllerTest class can be found below.
Controller.java
#RestController
public class Controller {
private final Service service;
#Autowired
public Controller(Service service) {
this.service = service;
}
#PostMapping("/test")
public Mono<String> test() {
return service.test(LocalDateTime.now())
.then(Mono.just("SUCCESS"));
}
}
ControllerTest.java
#WebFluxTest(controllers = Controller.class)
public class ControllerTest {
#MockBean
Service service;
#Autowired
WebTestClient webTestClient;
#Test
public void shouldPostRequestToEndpoint() {
given(service.test(any(LocalDateTime.class))).willReturn(empty());
webTestClient
.post()
.uri("/test")
.exchange()
.expectStatus().isOk()
.expectBody(String.class);
}
}
Even if you guys point me towards identifying what Binder needs to be registered, it would be great.
Thanks in advance!

It looks like spring context can't load. Is your JUnit version different between local and CI? If you use JUnit4, try adding #RunWith(SpringRunner.class) to your test.

Related

How to exclude Mongo spring data configuration only for tests

I am doing a simple sample project with Spring boot and data.
#Configuration
public class MongoConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(MongoConfig.class);
#Value("${mongo.uri}")
private String mongoUri;
public #Bean MongoClient mongoClient() {
LOGGER.info(" creating connection with mongodb with uri [{}] ", mongoUri);
return MongoClients.create(mongoUri);
}
}
This works fine and connects to mongo on startup. However, the tests also pick this up in autoscan. What is the best practice to make sure that mongo config gets excluded for tests?
If I add #WebMvcTest to the tests, it works. But not all tests will be mvc tests. I might be testing a utility class.
If I try using profiles, it gives my an error java.lang.IllegalStateException: The following classes could not be excluded because they are not auto-configuration classes: ...MongoConfig
#SpringBootTest
#ActiveProfiles("test")
class ApplicationTests {
#Test
void contextLoads() {
}
}
Please tell me a repeatable practice as I will be using it for all my tests.
The simplest way of doing it is to exclude your configuration bean when your test profile is active
#Profile("!test")
#Configuration
public class MongoConfig {
...
}
Here you tell to the BeanFactory to not create this bean if the profile test is present
Ref: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Profile.html

How to correctly setup Spring Boot 2.2 REST full integration test with JUnit5

I have simple controller, facade, set of services and repository for my H2 DB for Customer entity. It works fine but I need an E2E integration test with whole application context. And with JUnit5.
I've tried many setups, many times got errors like found multiple declarations of #BootstrapWith for test. Finally I made it work, but there is an issue with Spring JPA CustomerRepository which is an interface. I've never tried to use it like this so I have no experiance with it. But simply speaking, it won't create any bean of this type since it's just an interface. As the spring boot application, it works this way, but in the integration test it won't.
The error is still the same: Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [xxx.CustomerRepository].
This is my test setup:
#ExtendWith(SpringExtension.class)
#AutoConfigureMockMvc
#ContextConfiguration(classes = {
CustomerController.class,
CustomerFacade.class,
CustomerTerminationService.class,
CustomerRepository.class
})
#WebMvcTest(CustomerController.class)
#Import(CustomerRepository.class)
class CustomerFullIntegrationTest {
#Autowired
private MockMvc mockMvc;
#Test
void getCustomer() throws Exception {
mockMvc.perform(MockMvcRequestBuilders
.get("/customer/1")
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.lastName").exists())
.andExpect(jsonPath("$.products").isNotEmpty());
}
}
This is my repository:
public interface CustomerRepository extends JpaRepository<Customer, Long> {}
This is my Application:
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(ProfApplication.class, args);
}
}
Controller, facade, services are irrelevant.
Can you tell me, what exactly am I doing wrong?

Spring Boot #RestClientTest how test with real server (not mock)

I need to create an integration test against a REST API. My service is using Resttemplate as HTTP client. The client code is generated from swagger file.
Running the test yields an error java.lang.AssertionError: No further requests expected: HTTP GET
It seems that the test is running against a mock server. How to let the test run against the real server?
This is my current test setup (want to cut out a minimal test frame to get a fast test - booting the complete context is far too slow):
#RunWith(SpringRunner.class)
#Import(value = { TpzConfig.class, TpzServiceRestImpl.class, ManufacturingPlantPhPmMapperImpl.class,
ProductHierarchyMapperImpl.class, PlantMapperImpl.class })
#ActiveProfiles(profiles = { "tpz" })
#RestClientTest
public class TpzServiceRestImplTest {
#Autowired
private TpzService to;
#MockBean
private ProductionPlantService ppService;
#MockBean
private ProductHierarchyService phService;
#Test
public void test() {
List<ProductManufacturer> pmByProductHierarchy = to.pmByProductHierarchy("001100909100100388");
}
}
I need #RestClientTest to have a bean of RestTemplateBuilder.
Is there a way to configure #RestClientTest to use the real server (similar to #DataJpaTest where i can configure not to use h2)?
#RestTemplateTest give you pre-configured RestTemplateBuilder and MockRestServiceServer.
1.You could #Autowired MockRestServiceServer and mock expected HTTP calls.
2.Remove the auto configuration :
#RestClientTest(excludeAutoConfiguration = MockRestServiceServerAutoConfiguration.class)
But that make the test kind of slow.. There is maybe a way to optimize it.
3.In another hand, you could remove #RestClientTest and in a test configuration file, create a bean of RestTemplateBuilder. Something like this :
#TestConfiguration
public class TestConfig {
#Bean
public RestTemplateBuilder getRestTemplateBuilder() {
return new RestTemplateBuilder();
}
}
After this, add this configuration file in your imports :
#Import(value = { TpzConfig.class, TpzServiceRestImpl.class,
ManufacturingPlantPhPmMapperImpl.class, ProductHierarchyMapperImpl.class,
PlantMapperImpl.class, TestConfig.class })
And you should be good for your test.

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

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.

Spring Boot - Test for controller fails with 404 code

I want to write a test for controller. Here is test snippet:
#RunWith(SpringRunner.class)
#WebMvcTest(WeatherStationController.class)
#ContextConfiguration(classes = MockConfig.class)
public class WeatherStationControllerTest {
#Autowired
private MockMvc mockMvc;
#Autowired
private IStationRepository stationRepository;
#Test
public void shouldReturnCorrectStation() throws Exception {
mockMvc.perform(get("/stations")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}
controller code snippet:
#RestController
#RequestMapping(value = "stations")
public class WeatherStationController {
#Autowired
private WeatherStationService weatherService;
#RequestMapping(method = RequestMethod.GET)
public List<WeatherStation> getAllWeatherStations() {
return weatherService.getAllStations();
}
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public WeatherStation getWeatherStation(#PathVariable String id) {
return weatherService.getStation(id);
}
MockConfig class:
#Configuration
#ComponentScan(basePackages = "edu.lelyak.repository")
public class MockConfig {
//**************************** MOCK BEANS ******************************
#Bean
#Primary
public WeatherStationService weatherServiceMock() {
WeatherStationService mock = Mockito.mock(WeatherStationService.class);
return mock;
}
Here is error stack trace:
java.lang.AssertionError: Status
Expected :200
Actual :404
I can get what is wrong here.
How to fix test for controller?
HTTP code 404, means no resource found (on the server) for your request, which I think that your controller is not visible(let me say is not scanned) by spring boot.
A simple solution is scanning a parent package in MockConfig class, so spring can pick up all beans,
#ComponentScan(basePackages = "edu.lelyak") // assuming that's the parent package in your project
if you don't like this approach, you can add the controller's package name in basePackages
#ComponentScan(basePackages = {"edu.lelyak.controller","edu.lelyak.repository")
BTW, you don't have to manually set up WeatherStationService in MockConfig class, Spring boot can inject a mock for you and automatically reset it after each test method, you should just declare it in your test class:
#MockBean
private IStationRepository stationRepository;
On the other hand, you should mock weatherService.getAllStations() before calling get("/stations") in your test method (as you're not running integration test), so you can do:
List<WeatherStation> myList = ...;
//Add element(s) to your list
Mockito.when(stationService.getAllStations()).thenReturn(myList);
You can find more in :
Testing improvements in Spring Boot 1.4
Spring Boot features: Testing
I had the same issue. The controller was not getting picked up despite specifying it with #WebMvcTest(MyController.class). This meant all of its mappings were ignored, causing the 404. Adding #Import(MyController.class) resolved the issue, but I didn't expect the import to be necessary when I'm already specifying which controller to test.
I am not sure why your test is not working. But I got another solution which works for me.
#SpringBootTest
public class ControllerTest {
#Autowired
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(new TestController()).build();
}
#Test
public void shouldReturnCorrectStation() throws Exception {
mockMvc.perform(get("/stations")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}
After some debugging, it appears that the target controller is simply not registered as a method handler. Spring scans the beans for the presence of RestController annotation.
But the problem is that the annotation could be found only if the bean is proxied via CGLIB, but for the case when we use WebMvcTest it's proxied by JDK.
As a result, I searched for the configuration which is responsible for making the choice, and the one was finally found AopAutoConfiguration. So when SpringBootTest is used this one is autoloaded when you need WebMvcTest+PreAuthorize in your controllers, then simply use:
#Import(AopAutoConfiguration.class)
I import external configuration class by #ContextConfiguration(classes = MyConfig.class)
When I changed in MyConfig annotation #Configuration into #TestConfiguration it started to work properly.
I couldn't find a good answer but I could find one of the causes.
I was using in my tests the #PreAuthorize on the RestController.
You can mock the Oauth with this tip on the integration tests that use SpringBootTest. For SpringBootTest, this works very well too, but using SpringBootTest you load a lot of other resources (like JPA) that is not necessary to do a simple Controller test.
But with #WebMvcTest this not works as expected. The use of the WithMockOAuth2Scope annotation can be enough to stop the 401 error from authentication problem, but after that the WebMvcTest can't find the rest endpoint, returning the 404 error code.
After removing the #PreAuthorize on Controller, the test with WebMvcTest pass.
Based on the accepted answer, in my case I had copied and modified the file based on another test, but forgot to change the name for the controller on the top of the class, that being the reason why it was not finding the resource, as the error says.
#RunWith(SpringRunner.class)
#WebMvcTest(AnswerCommandController.class)
public class AnswerCommandControllerTest {
Here is a different approach to the controller test that worked for me.
Assumption: The class WeatherStationService is a #SpringBootApplication
Then, the test class below should work for you:
#RunWith(SpringRunner.class)
#SpringApplicationConfiguration(WeatherStationService.class)
#WebIntegrationTest
public class WeatherStationControllerTest {
#Autowired
private WebApplicationContext context;
MockMvc mockMvc;
#Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}
#Test
public void shouldReturnCorrectStation() throws Exception {
mockMvc.perform(get("/stations")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk();
}
}
With this test setup, you should no longer need the MockConfig class.
In my case it was about a missing starting slash /
I've appended / to both RequestMapping value and MockHttpServletRequestBuilder post urlTemplate parameters as first character.
In case anyone is wondering.
If we don't use #ContextConfiguration, #WebMvcTest annotation will load the REST controller class. Otherwise, when we use use #ContextConfiguration, seems ContextConfiguration clear the context REST controller config. We need to add the REST controller to ContextConfiguration such as:
#ContextConfiguration(classes = {MockConfig.class, WeatherStationController.class})

Resources