Global exception handling in spring webflux during tests - java

I'm developing a service with spring webflux. I implemented exception handling using #ControllerAdvice. It works pretty well, but when I run integration tests it seems that #ControllerAdvice annotated component is not loaded, resulting in this response:
{
"timestamp":"2019-11-28T08:56:47.285+0000",
"path":"/fooController/bar",
"status":500,
"error":"Internal Server Error",
"message":"java.lang.IllegalStateException: Could not resolve parameter [1] in protected org.springframework.http.ResponseEntity<it.test.model.Response> it.test.exception.ExceptionHandlerController.handleServiceException(java.lang.Exception,org.springframework.web.context.request.WebRequest): No suitable resolver
}
This is my controller advice:
#ControllerAdvice
public class ExceptionHandlerController extends ResponseEntityExceptionHandler {
private final Logger logger = LoggerFactory.getLogger(ExceptionHandlerController.class);
#ExceptionHandler
protected ResponseEntity<Response> handleServiceException(Exception ex, WebRequest request) {
this.logger.error("Error occurred: \"{}\"", ex.getMessage());
Response<Foo> response = new Response<>(new Foo(),
"generic error",
HttpStatus.INTERNAL_SERVER_ERROR);
return new ResponseEntity<>(response, null, HttpStatus.OK);
}
}
And this is my integration test class
#ExtendWith(SpringExtension.class)
#WebFluxTest(MyController.class)
#ContextConfiguration(classes = {MyController.class, MyServiceImpl.class, ExceptionHandlerController.class })
public class MyControllerIT {
#Autowired
private WebTestClient webTestClient;
#Test
public void testShouldFail() throws IOException {
return this.webTestClient.get()
.uri(uri)
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.statusCode").isEqualTo(500);
}
}

if you read the documentation for #WebFluxTest it states:
Annotation that can be used for a Spring WebFlux test that focuses
only on Spring WebFlux components.
Using this annotation will disable full auto-configuration and instead
apply only configuration
relevant to WebFlux tests (i.e. #Controller, #ControllerAdvice,
#JsonComponent, Converter/GenericConverter, and WebFluxConfigurer
beans but not #Component, #Service or #Repository beans).
Typically #WebFluxTest is used in combination with #MockBean or
#Import to create any collaborators required by your #Controller
beans.
If you are looking to load your full application configuration and use
WebTestClient, you should consider #SpringBootTest combined with
#AutoConfigureWebTestClient rather than this annotation.
This means that
#ContextConfiguration(classes = {MyController.class, MyServiceImpl.class, ExceptionHandlerController.class })
is not what u use here. The #WebFluxTest annotation does not load #Component, #Service or #Repository
It is mainly used to test RestControllers only, and their advices.
The options you have seem to be:
loading MyController.class and then mock any dependency this class has (MyServiceImpl.class)
load a full Context using #SpringBootTest instead combined with
#AutoConfigureWebTestClient

Just a not on top of the already accepted answer which is totally correct.
The usage of #ContextConfiguration is necessary only for testing Functional endpoints. I Think this is probably what confuses people.
#WebFluxTest is used during functional endpoint tests for starting the web context and server.
But since we dont use controllers, we have to use #ContextConfiguration to bring the handler and routerFunction beans in our spring context.
In this case since we use annotated controller a simple #WebFluxTest(Controller.class) is more than enough for unit testing.

Adding controllerAdvice in the WebTestClient solved the issue.
WebTestClient.bindToController(controller)
.controllerAdvice(errorControllerAdvice)
.build()

Related

Mock JWT Utils to validate Token

I want to create JUnkt test for this endpoint:
#Autowired
private JwtTokenProvider jwtTokenProvider;
#PostMapping("reset_token")
public ResponseEntity<?> resetToken(#Valid #RequestBody ResetPasswordTokenDTO resetPasswordTokenDTO, BindingResult bindResult) {
final String login = jwtTokenProvider.getUsername(resetPasswordTokenDTO.getResetPasswordToken());
}
Full code: Github
JUnit test:
#Test
public void resetTokenTest_NOT_FOUND() throws Exception {
when(usersService.findByResetPasswordToken(anyString())).thenReturn(Optional.empty());
mockMvc.perform(post("/users/reset_token")
.contentType(MediaType.APPLICATION_JSON)
.content(ResetPasswordTokenDTO))
.andExpect(status().isNotFound());
}
I get NPE at this line when I run the code:
final String login = jwtTokenProvider.getUsername(resetPasswordTokenDTO.getResetPasswordToken());
How I can mock jwtTokenProvider properly? As you can see I have a file with test data which I load but the token is not extracted. Do you know how I can fix this issue?
The most straightforward way is to use Mockito and create mock instances and pass it directly to your controller class using constructor injection.
However, if you do not wish to use constructor injection (I recommend you to use it though, as it is much more explicit) you need to define your beans in a separate test configuration class
#Profile("test")
#Configuration
public class TestConfiguration {
#Bean
public JwtTokenProvider mockJwtTokenProvider() {
return Mockito.mock(JwtTokenProvider.class);
}
}
Also, add the correct profile to your test class by #ActiveProfiles("test")
You can consider using a #MockBean directly in your test class to mock your JwtTokenProvider. #MockBean annotation is Spring-ish and is included in spring-boot-starter-test. The Spring Boot documentation summarizes it well:
Spring Boot includes a #MockBean annotation that can be used to define
a Mockito mock for a bean inside your ApplicationContext. You can use
the annotation to add new beans or replace a single existing bean
definition. The annotation can be used directly on test classes, on
fields within your test, or on #Configuration classes and fields. When
used on a field, the instance of the created mock is also injected.
Mock beans are automatically reset after each test method.
The #MockBean annotation will make Spring look for an existing single bean of type JwtTokenProvider in its application context. If it exists, the mock will replace that bean, and if it does not exist, it adds the new mock in the application context.
Your test class would look like this:
import org.springframework.boot.test.mock.mockito.MockBean;
#MockBean
#Qualifier("xxx") //If there is more than one bean of type JwtTokenProvider
private JwtTokenProvider jwtTokenProvider;
#Test
public void resetTokenTest_NOT_FOUND() throws Exception {
when(jwtTokenProvider.getUsername(anyString())).thenReturn(Optional.empty());
mockMvc.perform(post("/users/reset_token")
.contentType(MediaType.APPLICATION_JSON)
.content(ResetPasswordTokenDTO))
.andExpect(status().isNotFound());
}
You might also want to check this and this.

Spring Boot Error #Autowired RestTemplateBuilder with junit

Trying to #Autowired a RestTemplate in Spring Boot 2.1.4 using RestTemplateBuilder.
When i run junit tests i get an error when trying to autowired RestTemplate.
I saw here: How to autowire RestTemplate using annotations
It seems that RestTemplateBuilder is better so i would like to use that.
This is the configuration file :
#Configuration
public class Beans {
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
This is the test class:
#RunWith(SpringRunner.class)
#SpringBootTest
#ContextConfiguration(classes = Beans.class)
public class AppTest extends TestCase {
#Autowired
private RestTemplate restTemplate;
}
The error is :
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of method restTemplate in beanDeclerations.Beans required a bean of type 'org.springframework.boot.web.client.RestTemplateBuilder' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'org.springframework.boot.web.client.RestTemplateBuilder' in your configuration.
I edited out other autowired that work.
What am i missing here?
After searching the web i found out that spring auto wired RestTemplateBuilder, why isn't doing so here?
EDIT:
I ended up using #RestClientTest() and had to move RestTemplateBuilder Bean to the main class for now, I'll move it later to a different place.
Thanks for the help.
The RestTemplateBuilder should be available through an autocofiguration (see: https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestTemplateAutoConfiguration.java). I think this config is missing because of your #ContextConfiguration. You have some possibilities. Try to add the AutoConfig for the RestTemplateBuilder to your ContextConfiguration. Second one is to make a TestConfiguration and create your own RestTemplateBuilder or directly a RestTemplate. The third one is don't inject the RestTemplate - build it by hand in your test. You can also remove the #ContextConfiguration-Annotation but this will result in a test which loads every config you have defined in your project.
There's also a RestTestTemplate (https://www.baeldung.com/spring-boot-testresttemplate) which is designed for tests.
#RunWith(SpringRunner.class)
#SpringBootTest
#ContextConfiguration(classes = {TestConfig.class, RestTemplateAutoConfiguration.class})
public class SandboxApplicationTests {
#Autowired
RestTemplate restTemplate;
#Test
public void contextLoads() {
}
}
The snippet above works for me. Without the RestTemplateAutoConfiguration in the ContextConfiguration the RestTemplate can't be created because of the missing RestTemplateBuilder-Bean
I had the same issue (in an Spring Boot 2.5.5 application).
To solve it I had to add #AutoConfigureWebClient on my test class.
So my test looks like this :
#AutoConfigureWebClient
#WebMvcTest(controllers = TeamController.class)
public class TeamControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private FooService fooService;
#Test
public void findAllFoos() throws Exception {
mockMvc.perform(get("/foos"))
.andExpect(status().isOk());
}
}
But I read somewhere it may be considered as a bug that should be fixed by Spring Team (maybe will this annotation be directly added in #WebMvcTest ?).
I think this way: the test starts and stops faster than when you use #SpringBootTest (because that last one makes your application start entirely).
For more info : https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureWebClient.html

How to enable Autowired for FeignClient in JHipster?

I have a microservice application and i want to enable it to call API.
FeignClientConfiguration.java
#Configuration
#Profile("!test")
#EnableFeignClients(basePackages = "blabla")
public class FeignClientConfiguration {
}
And then spring boot App :
#ComponentScan
#EnableAutoConfiguration(exclude ={MetricFilterAutoConfiguration.class,MetricRepositoryAutoConfiguration.class})
#EnableConfigurationProperties({LiquibaseProperties.class, ApplicationProperties.class})
#EnableDiscoveryClient
public class MyApp { }
The feign client
#FeignClient()
public interface ExtClient { ... }
I then tried to autowired the client
Mytransaction.java
public class MyTransaction {
#Autowired
ExtClient txnClient;
....
}
But it fails with a NPE. How to autowired FEIGN in JHipster?
MyTransaction must be a Spring bean. Simplest way is to annotate it with #Service, this way it'll get instantiated by Spring and txnClient will get injected. By the way, you should consider using constructor injection rather than field injection, many examples in JHipster generated code.

Spring Boot - Test Cases - Dont Load All Components

I am trying to to rest my rest classes in Spring MVC
If I run the following code (ran fine when the project was small but now fails) it tries to load all the different components in my application.
This includes beans which interact with external systems and need credentials in order to connect
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class TestDummyRest extends BaseRestTestCase{
#Autowired
private MockMvc mockMvc;
#MockBean
private IDummyServices mockDummyServices;
#Test
public void getSendGoodMessage() throws Exception {
given(mockDummyServices.sendGoodMessage(Mockito.anyString())).willReturn(true);
mockMvc.perform(get("/dummy"))
.andExpect(status().isOk())
.andExpect(content().contentType(TEXT_PLAIN_CONTENT_TYPE));
verify(mockDummyServices, times(1)).sendGoodMessage(Mockito.anyString());
}
}
How do I tell my test classes not to load the #Configuration or #Component classes of my application?
Instead of not creating other classes in your application, you could only create the classes you are interested in, see 15.6.1 Server-Side Tests - Setup Options
The second is to simply create a controller instance manually without
loading Spring configuration. Instead basic default configuration,
roughly comparable to that of the MVC JavaConfig or the MVC namespace,
is automatically created and can be customized to a degree:
public class MyWebTests {
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();
}
// ...
}
You need to use #TestComponent and #TestConfiguration for this as explained in Spring doc here

Difference between MockMvc and RestTemplate in integration tests

Both MockMvc and RestTemplate are used for integration tests with Spring and JUnit.
Question is: what's the difference between them and when we should choose one over another?
Here are just examples of both options:
//MockMVC example
mockMvc.perform(get("/api/users"))
.andExpect(status().isOk())
(...)
//RestTemplate example
ResponseEntity<User> entity = restTemplate.exchange("/api/users",
HttpMethod.GET,
new HttpEntity<String>(...),
User.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
As said in this
article you should use MockMvc when you want to test Server-side of application:
Spring MVC Test builds on the mock request and response from spring-test and does not require a running servlet container. The main difference is that actual Spring MVC configuration is loaded through the TestContext framework and that the request is performed by actually invoking the DispatcherServlet and all the same Spring MVC infrastructure that is used at runtime.
for example:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration("servlet-context.xml")
public class SampleTests {
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = webAppContextSetup(this.wac).build();
}
#Test
public void getFoo() throws Exception {
this.mockMvc.perform(get("/foo").accept("application/json"))
.andExpect(status().isOk())
.andExpect(content().contentType("application/json"))
.andExpect(jsonPath("$.name").value("Lee"));
}}
And RestTemplate you should use when you want to test Rest Client-side application:
If you have code using the RestTemplate, you’ll probably want to test it and to that you can target a running server or mock the RestTemplate. The client-side REST test support offers a third alternative, which is to use the actual RestTemplate but configure it with a custom ClientHttpRequestFactory that checks expectations against actual requests and returns stub responses.
example:
RestTemplate restTemplate = new RestTemplate();
MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate);
mockServer.expect(requestTo("/greeting"))
.andRespond(withSuccess("Hello world", "text/plain"));
// use RestTemplate ...
mockServer.verify();
also read this example
With MockMvc, you're typically setting up a whole web application context and mocking the HTTP requests and responses. So, although a fake DispatcherServlet is up and running, simulating how your MVC stack will function, there are no real network connections made.
RestTemplate can conveniently be initialized with a custom ClientHttpRequestFactory. Implementations usually create ClientHttpRequest objects that open actual TCP/HTTP(s) connections. But you don't have to. You can provide a mock implementation where you can do whatever you want. In fact, this is how the MockRestServiceServer utility operates, you could use that.
It is possible to use both RestTemplate and MockMvc!
This is useful if you have a separate client where you already do the tedious mapping of Java objects to URLs and converting to and from Json, and you want to reuse that for your MockMVC tests.
Here is how to do it:
#RunWith(SpringRunner.class)
#ActiveProfiles("integration")
#WebMvcTest(ControllerUnderTest.class)
public class MyTestShould {
#Autowired
private MockMvc mockMvc;
#Test
public void verify_some_condition() throws Exception {
MockMvcClientHttpRequestFactory requestFactory = new MockMvcClientHttpRequestFactory(mockMvc);
RestTemplate restTemplate = new RestTemplate(requestFactory);
ResponseEntity<SomeClass> result = restTemplate.getForEntity("/my/url", SomeClass.class);
[...]
}
}

Categories

Resources