Enabling Cookies and Redirects in TestRestTemplate - spring

How to I add TestRestTemplate.HttpClientOption.ENABLE_REDIRECTS and TestRestTemplate.HttpClientOption.ENABLE_COOKIES to spring-boots TestRestTemplate?
I am using spring-boot so have a TestRestTemplate configured for me automatically.
I can customise this bean before creation using RestTemplateBuilder. The problem is I can't see how to add these options:
#Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder()
.errorHandler(new ResponseErrorHandler() {
...
});
}
The documentation has some constructors that accept these options but the problem is the bean has already been created for me.

You can create a TestRestTemplate and present it to Spring by using the #Bean annotation.
For example:
#Bean
#Primary
public TestRestTemplate testRestTemplate() {
RestTemplate restTemplate = new RestTemplateBuilder()
.errorHandler(new ResponseErrorHandler() {
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return false;
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
}
}).build();
return new TestRestTemplate(restTemplate, user, password, TestRestTemplate.HttpClientOption.ENABLE_REDIRECTS, TestRestTemplate.HttpClientOption.ENABLE_COOKIES);
}
Or, if you do not need to customise the RestTemplate then use the following constructor (which internally instances a RestTemplate for you):
#Bean
#Primary
public TestRestTemplate testRestTemplate() {
return new TestRestTemplate(TestRestTemplate.HttpClientOption.ENABLE_REDIRECTS, TestRestTemplate.HttpClientOption.ENABLE_COOKIES);
}
Update 1 to address this comment:
when I run my tests, I now get the following error org.apache.http.ProtocolException: Target host is not specified
The TestRestTemplate provided by Spring is configured to resolve paths relative to http://localhost:${local.server.port}. So, when you replace the Spring provided instance with your own instance you'll either have to provide the full address (including host and port) or configure your own TestRestTemplate with a LocalHostUriTemplateHandler (you can see this code in org.springframework.boot.test.context.SpringBootTestContextCustomizer.TestRestTemplateFactory). Here's an example of the latter approach:
#Bean
#Primary
public TestRestTemplate testRestTemplate(ApplicationContext applicationContext) {
RestTemplate restTemplate = new RestTemplateBuilder()
.errorHandler(new ResponseErrorHandler() {
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return false;
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
}
}).build();
TestRestTemplate testRestTemplate =
new TestRestTemplate(restTemplate, user, password, TestRestTemplate.HttpClientOption
.ENABLE_REDIRECTS, TestRestTemplate.HttpClientOption.ENABLE_COOKIES);
// let this testRestTemplate resolve paths relative to http://localhost:${local.server.port}
LocalHostUriTemplateHandler handler =
new LocalHostUriTemplateHandler(applicationContext.getEnvironment(), "http");
testRestTemplate.setUriTemplateHandler(handler);
return testRestTemplate;
}
With this bean configuration the following test case uses the customised TestRestTemplate and successfully invokes the Spring Boot app on localhost:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class RestTemplateTest {
#Autowired
private TestRestTemplate restTemplate;
#Test
public void test() {
ResponseEntity<String> forEntity = this.restTemplate.getForEntity("/some/endpoint", String.class);
System.out.println(forEntity.getBody());
}
}

Related

Why are my MockBeans and MockRestServiceServer not returning proper responses when testing JMS Listener in Spring Boot

I am having an issue when trying to integration test my JMS listener with Mockito and MockRestServiceServer. Even if I'm using the correct Mockito.when annotations, they are coming up as null, and the MockRestServiceServer is acting as if it isn't being called. If I switch instead to test against the myService component that the jms listener calls, the mocks and the MockRestServiceServer calls are working as expected, which is puzzling. I am connecting to an embedded ActiveMQ broker for the test and I am using Spring Boot 2.2.8.RELEASE and JDK 8.x if that helps.
Here is the JMS Listener Class
#Component
public class MyJmsListener {
#Autowired
private MyService myService;
#JmsListener(
destination = "${jms.queue}",
containerFactory = "myJmsListenerContainerFactory"
)
public void receive(Message<String> message) {
myService.process(message);
}
}
Here is the JMS Listener Test Class
#RunWith(SpringRunner.class)
#SpringBootTest
#ActiveProfiles("test")
public class JmsListenerTest {
...
#MockBean
private AuthorizationService authorizationService;
...
#Autowired
private MockRestServiceServer mockRestServiceServer;
#Autowired
private JmsTemplate listenerTestJmsTemplate;
#Value("${jms.queue}")
private String testDestination;
...
#Test
public void testListener() throws IOException, URISyntaxException, InterruptedException {
//ARRANGE
String payloadPath = "classpath:payloads/listenerPayload.json";
String payload = new String(Files.readAllBytes(ResourceUtils.getFile(payloadPath).toPath()));
String testAuth = "auth";
Mockito.when(authorizationService.generateTicket(Mockito.any(Headers.class), Mockito.eq("9130353887051456")))
.thenReturn(testAuth);
String extPayloadPath = "classpath:payloads/revokeCancelAutoRenewRequestApi.json";
String extPayload = new String(Files.readAllBytes(ResourceUtils.getFile(extPayloadPath).toPath()));
mockRestServiceServer.expect(ExpectedCount.once(), MockRestRequestMatchers.requestTo(new URI("/test/v3/subscriptions/400367048/something")))
.andExpect(MockRestRequestMatchers.content().string(extPayload))
.andExpect(MockRestRequestMatchers.header(HttpHeaders.AUTHORIZATION, testAuth))
.andRespond(MockRestResponseCreators.withStatus(HttpStatus.OK));
//ACT
listenerTestJmsTemplate.convertAndSend(testDestination, payload);
//ASSERT
mockRestServiceServer.verify();
Assert.assertTrue(JmsListenerWrapperConfiguration.latch.await(5, TimeUnit.SECONDS));
}
...
}
I have a JmsListenerWrapperConfiguration that will allow me to wrap the countdown latch into the jms listener.
#Configuration
#Profile("test")
public class JmsListenerWrapperConfiguration {
public static final CountDownLatch latch = new CountDownLatch(1);
#Bean
public JmsTemplate listenerTestjmsTemplate(ActiveMQConnectionFactory activeMQConnectionFactory){
JmsTemplate jmsTemplate = new JmsTemplate(activeMQConnectionFactory);
return jmsTemplate;
}
/**
* Wrap the JMS Listeners with a count down latch that will allow us to unit test them.
* #return The bean post processor that will wrap the JMS Listener.
*/
#Bean
public static BeanPostProcessor listenerWrapper() {
return new BeanPostProcessor() {
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof MyJmsListener) {
MethodInterceptor interceptor = new MethodInterceptor() {
#Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Object result = invocation.proceed();
if (invocation.getMethod().getName().equals("listen")) {
latch.countDown();
}
return result;
}
};
if (AopUtils.isAopProxy(bean)) {
((Advised) bean).addAdvice(interceptor);
return bean;
}
else {
ProxyFactory proxyFactory = new ProxyFactory(bean);
proxyFactory.addAdvice(interceptor);
return proxyFactory.getProxy();
}
}
else {
return bean;
}
}
};
}
}
The MockRestServiceServer configuration is defined here.
#Configuration
#Profile("test")
public class MockRestServiceServerConfiguration {
#Bean
public MockRestServiceServer mockRestServiceServer(RestTemplate restTemplate) {
MockRestServiceServerBuilder builder = MockRestServiceServer.bindTo(restTemplate);
MockRestServiceServer server = builder.bufferContent().build();
return server;
}
}
The error that I see is as follows.
java.lang.AssertionError: Further request(s) expected leaving 1 unsatisfied expectation(s).
0 request(s) executed.
at org.springframework.test.web.client.AbstractRequestExpectationManager.verify(AbstractRequestExpectationManager.java:159)
at org.springframework.test.web.client.MockRestServiceServer.verify(MockRestServiceServer.java:116)
Update
I've been debugging and of course the test is running on thread[main], whereas the JMS listener is running on thread[DefaultMessageListenerContainer-1], so my question then becomes, what should we do with Mockito mocking when the mocks/verifications need to be used by separate threads?
It turns out that the MockRestServiceServer needed to verify after the latch is awaiting as shown in this code below.
Assert.assertTrue(JmsListenerWrapperConfiguration.latch.await(5, TimeUnit.SECONDS));
mockRestServiceServer.verify();

Spring-boot: Beans are null during unit/integration test

I am new to sprint-boot. I have a spring-boot application which is working fine in it's regular path. Now as I am trying to write unit/integration tests, I find that my beans are null.
I appreciate any help on understanding why are they null and how to fix it. It seems that it is not able to pick up properties from the yml at all.Please let me know if any more clarification is required.
To clarify the structure:
The main class:
#SpringBootApplication
#EnableConfigurationProperties(ApplicationConfiguration.class)
public class Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Application.class, args);
}
}
The properties file (src/main/java/resources/application.yml)
http:
url:
protocol: http
baseUrl: ${CONNECTOR_BASE_URL}
connectorListUrl : connectors
The configuration class that is using the above properties (ApplicationConfiguration.java) is :
#ConfigurationProperties(prefix = "http.url")
#Validated
#Data
public class ApplicationConfiguration {
private String protocol;
private String baseUrl;
private String connectorListUrl;
}
Now, the simplified version of the class(ContinuousMonitorServiceTask.java that I am trying to write my test on, looks like :
#Component
#Slf4j
public class ContinuousMonitorServiceTask extends TimerTask {
#Autowired MonitorHttpClient httpClient;
#Autowired ApplicationConfiguration config;
#PostConstruct
public void setUp() {
connectorListUrl =
config.getProtocol() + "://" + config.getBaseUrl() + "/" + config.getConnectorListUrl();
connectorListHeaderParams.clear();
connectorListHeaderParams.put("Accept", "application/json");
connectorListHeaderParams.put("Content-Type", "application/json");
connectorListGetRequest = new HttpGet(connectorListUrl);
httpClient.setHeader(connectorListGetRequest, connectorListHeaderParams);
}
public void fetchList() {
try {
response = httpClient.callApi("Get Connector List", connectorListGetRequest);
log.info(response.toString());
connectorListResponseHandler(response);
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
The above code is working fine when I am executing.
Now when I am writing test, I need to mock api calls and hence, I have used MOCK-SERVER and my testSimple1 test has passed which is a simple test to see if the mock server can start and return expected response. However, while debugging simpleTest2, I am seeing
monitorTask is null
appConfig is null
monitorTask is null
Although, I have src/test/resources/application.yml as:
http:
url:
protocol: http
baseUrl: 127.0.0.1:8080
connectorListUrl : connectors
My guess is that appConfig is not able to pick up the properties from application.yml during test and hence everything is null.However, I am not 100% sure about what is happening in real time.
Here is how my test class looks like (Kind of dirty code, but I am putting it in it's current state to show what I have tried so far):
//#RunWith(MockitoJUnitRunner.class)
//#TestPropertySource(locations="classpath:application.yml")
//#RunWith(SpringJUnit4ClassRunner.class)
//#SpringApplicationConfiguration(ApplicationConfiguration.class)
#RunWith(SpringRunner.class)
#SpringBootTest(classes = ApplicationConfiguration.class)
//#EnableConfigurationProperties(ApplicationConfiguration.class)
public class ContinousMonitorTest {
private MockMvc mockMvc;
#Mock private MonitorHttpClient httpClient;
#Mock private ApplicationConfiguration appConfig;
#InjectMocks
//#MockBean
//#Autowired
private ContinuousMonitorServiceTask monitorTask;
TestRestTemplate restTemplate = new TestRestTemplate();
HttpHeaders headers = new HttpHeaders();
private static ClientAndServer mockServer;
#BeforeClass
public static void startServer() {
mockServer = startClientAndServer(8080);
}
#AfterClass
public static void stopServer() {
mockServer.stop();
}
private void createExpectationForInvalidAuth() {
new MockServerClient("127.0.0.1", 8080)
.when(
request()
.withMethod("GET")
.withPath("/validate")
.withHeader("\"Content-type\", \"application/json\""),
//.withBody(exact("{username: 'foo', password: 'bar'}")),
exactly(1))
.respond(
response()
.withStatusCode(401)
.withHeaders(
new Header("Content-Type", "application/json; charset=utf-8"),
new Header("Cache-Control", "public, max-age=86400"))
.withBody("{ message: 'incorrect username and password combination' }")
.withDelay(TimeUnit.SECONDS,1)
);
}
private GenericResponse hitTheServerWithGETRequest() {
String url = "http://127.0.0.1:8080/validate";
HttpClient client = HttpClientBuilder.create().build();
HttpGet post = new HttpGet(url);
post.setHeader("Content-type", "application/json");
GenericResponse response=null;
try {
StringEntity stringEntity = new StringEntity("{username: 'foo', password: 'bar'}");
post.getRequestLine();
// post.setEntity(stringEntity);
response=client.execute(post, new GenericResponseHandler());
} catch (Exception e) {
throw new RuntimeException(e);
}
return response;
}
#Test
public void testSimple1() throws Exception{
createExpectationForInvalidAuth();
GenericResponse response = hitTheServerWithGETRequest();
System.out.println("response customed : " + response.getResponse());
assertEquals(401, response.getStatusCd());
monitorTask.fetchConnectorList();
}
#Test
public void testSimple2() throws Exception{
monitorTask.fetchConnectorList();
}
as #second suggested above, I made a change in the testSimple2 test to look like and that resolved the above mentioned problem.
#Test
public void testSimple2() throws Exception{
mockMvc = MockMvcBuilders.standaloneSetup(monitorTask).build();
Mockito.when(appConfig.getProtocol()).thenReturn("http");
Mockito.when(appConfig.getBaseUrl()).thenReturn("127.0.0.1:8080");
Mockito.when(appConfig.getConnectorListUrl()).thenReturn("validate");
Mockito.when(httpClient.callApi(Mockito.any(), Mockito.any())).thenCallRealMethod();
monitorTask.setUp();
monitorTask.fetchConnectorList();
}
Alternatively, could have done:
#Before
public void init()
{
MockitoAnnotations.initMocks(this);
}

Autowired RibbonAware RestTemplate with Interceptor

I am having a wrapper that autowire ribbon aware RestTemplate and then I add a interceptor-
#Component
public class MyRestTemplate {
#Autowired
private RestTemplate restTemplate;
#PostConstruct
public void init(){
restTemplate.setInterceptors(Collections.singletonList(new
MyRestTemplateRequestInterceptor(applicationName)));
}
}
If I #AutoWire RestTemplate in some other component, will I get same instance of RestTemplate with this incterceptor? My observation is the autowired resttemplate in other component also adds this interceptor. Please help to validate the understanding.
Yyou will get the same instance of RestTemplate. When the bean MyRestTemplate is constructor (rather after it's construction), you are adding the MyRestTemplateRequestInterceptor to the RestTemplate interceptor.
So this interceptor will be applied to RestTemplate.
If you are looking to apply an interceptor for a specific feign client you can create your own configuration:
#Configuration
#IgnoreDuringScan
public class FeignClientConfiguration {
#Autowired
private Environment environment;
#Bean
public Encoder feignEncoder() {
return new GsonEncoder();
}
#Bean
public ErrorDecoder feignDecoder() {
return new GsonDecoder();
}
#Bean
public RequestInterceptor requestHeaderInterceptor() {
return new RequestInterceptor() {
#Override
public void apply(RequestTemplate requestTemplate) {
//impl
}
};
}
#Bean
public Feign.Builder feingBuilder() {
return Feign.builder().encoder(feignEncoder()).decoder(feignDecoder()).errorDecoder(new YourErrorDecoder())
.requestInterceptor(requestHeaderInterceptor);
}
}
Then just apply the configuration to your feign client:
#FeignClient(name = "myFeign", url = "url/...", configuration = {
FeignClientConfiguration.class
})
public interface YourFeignClient {
//....
}

Spring Boot Apache Camel Routes testing

I have a Springboot application, where I have some Camel routes configured.
public class CamelConfig {
private static final Logger LOG = LoggerFactory.getLogger(CamelConfig.class);
#Value("${activemq.broker.url:tcp://localhost:61616}")
String brokerUrl;
#Value("${activemq.broker.maxconnections:1}")
int maxConnections;
#Bean
ConnectionFactory jmsConnectionFactory() {
PooledConnectionFactory pooledConnectionFactory = new PooledConnectionFactory(new ActiveMQConnectionFactory(brokerUrl));
pooledConnectionFactory.setMaxConnections(maxConnections);
return pooledConnectionFactory;
}
#Bean
public RoutesBuilder route() {
LOG.info("Initializing camel routes......................");
return new SpringRouteBuilder() {
#Override
public void configure() throws Exception {
from("activemq:testQueue")
.to("bean:queueEventHandler?method=handleQueueEvent");
}
};
}
}
I want to test this route from activemq:testQueue to queueEventHandler::handleQueueEvent.
I tried different things mentioned here http://camel.apache.org/camel-test.html, but doesn't seem to get it working.
I am trying to do something like this:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {CamelConfig.class, CamelTestContextBootstrapper.class})
public class CamelRouteConfigTest {
#Produce(uri = "activemq:testQueue")
protected ProducerTemplate template;
#Test
public void testSendMatchingMessage() throws Exception {
template.sendBodyAndHeader("testJson", "foo", "bar");
// Verify handleQueueEvent(...) method is called on bean queueEventHandler by mocking
}
But my ProducerTemplate is always null. I tried auto-wiring CamelContext, for which I get an exception saying it cannot resolve camelContext. But that can be resolved by adding SpringCamelContext.class to #SpringBootTest classes. But my ProducerTemplate is still null.
Please suggest. I am using Camel 2.18 and Spring Boot 1.4.
In Camel 2.22.0 and ongoing, which supports Spring Boot 2 you can use the following template to test your routes with Spring Boot 2 support:
#RunWith(CamelSpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.NONE, classes = {
Route1.class,
Route2.class,
...
})
#EnableAutoConfiguration
#DisableJmx
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class RouteTest {
#TestConfiguration
static class Config {
#Bean
CamelContextConfiguration contextConfiguration() {
return new CamelContextConfiguration() {
#Override
public void beforeApplicationStart(CamelContext camelContext) {
// configure Camel here
}
#Override
public void afterApplicationStart(CamelContext camelContext) {
// Start your manual routes here
}
};
}
#Bean
RouteBuilder routeBuilder() {
return new RouteBuilder() {
#Override
public void configure() {
from("direct:someEndpoint").to("mock:done");
}
};
}
// further beans ...
}
#Produce(uri = "direct:start")
private ProducerTemplate template;
#EndpointInject(uri = "mock:done")
private MockEndpoint mockDone;
#Test
public void testCamelRoute() throws Exception {
mockDone.expectedMessageCount(1);
Map<String, Object> headers = new HashMap<>();
...
template.sendBodyAndHeaders("test", headers);
mockDone.assertIsSatisfied();
}
}
Spring Boot distinguishes between #Configuration and #TestConfiguration. The primer one will replace any existing configuration, if annotated on a top-level class, while #TestConfiguration will be run in addition to the other configurations.
Further, in larger projects you might run into auto-configuration issues as you can't rely on Spring Boot 2 to configure your custom database pooling or what not correctly or in cases where you have a specific directory structure and the configurations are not located within a direct ancestor directory. In that case it is proabably preferable to omit the #EnableAutoConfiguration annotation. In order to tell Spring to still auto-configure Camel you can simply pass CamelAutoConfiguration.class to the classes mentioned in #SpringBootTest
#SpringBootTest(webEnvironment = WebEnvironment.NONE, classes = {
Route1.class,
Route2.class,
RouteTest.Config.class,
CamelAutoConfiguration.class
}
As no automatic configuration is performed, Spring won't load the test configuration inside your test class nor initialize Camel as well. By adding those configs to the boot classes manually Spring will do it for you.
For one route with MQ and Spring Boot like this:
#Component
public class InboundRoute extends RouteBuilder {
#Override
public void configure() {
JaxbDataFormat personDataFormat = new JaxbDataFormat();
personDataFormat.setContextPath(Person.class.getPackage().getName());
personDataFormat.setPrettyPrint(true);
from("direct:start").id("InboundRoute")
.log("inbound route")
.marshal(personDataFormat)
.to("log:com.company.app?showAll=true&multiline=true")
.convertBodyTo(String.class)
.inOnly("mq:q.empi.deim.in")
.transform(constant("DONE"));
}
}
I use adviceWith in order to replace the endpoint and use only mocks:
#RunWith(CamelSpringBootRunner.class)
#UseAdviceWith
#SpringBootTest(classes = InboundApp.class)
#MockEndpoints("mock:a")
public class InboundRouteCamelTest {
#EndpointInject(uri = "mock:a")
private MockEndpoint mock;
#Produce(uri = "direct:start")
private ProducerTemplate template;
#Autowired
private CamelContext context;
#Test
public void whenInboundRouteIsCalled_thenSuccess() throws Exception {
mock.expectedMinimumMessageCount(1);
RouteDefinition route = context.getRouteDefinition("InboundRoute");
route.adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() {
weaveByToUri("mq:q.empi.deim.in").replace().to("mock:a");
}
});
context.start();
String response = (String) template.requestBodyAndHeader("direct:start",
getSampleMessage("/SimplePatient.xml"), Exchange.CONTENT_TYPE, MediaType.APPLICATION_XML);
assertThat(response).isEqualTo("DONE");
mock.assertIsSatisfied();
}
private String getSampleMessage(String filename) throws Exception {
return IOUtils
.toString(this.getClass().getResourceAsStream(filename), StandardCharsets.UTF_8.name());
}
}
I use the following dependencies: Spring Boot 2.1.4-RELEASE and Camel 2.23.2. The complete source code is available on Github.
This is how I did this finally:
#RunWith(SpringRunner.class)
public class CamelRouteConfigTest extends CamelTestSupport {
private static final Logger LOG = LoggerFactory.getLogger(CamelRouteConfigTest.class);
private static BrokerService brokerSvc = new BrokerService();
#Mock
private QueueEventHandler queueEventHandler;
#BeforeClass
// Sets up an embedded broker
public static void setUpBroker() throws Exception {
brokerSvc.setBrokerName("TestBroker");
brokerSvc.addConnector("tcp://localhost:61616");
brokerSvc.setPersistent(false);
brokerSvc.setUseJmx(false);
brokerSvc.start();
}
#Override
protected RoutesBuilder createRouteBuilder() throws Exception {
return new CamelConfig().route();
}
// properties in .yml has to be loaded manually. Not sure of .properties file
#Override
protected Properties useOverridePropertiesWithPropertiesComponent() {
YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
try {
PropertySource<?> applicationYamlPropertySource = loader.load(
"properties", new ClassPathResource("application.yml"),null);// null indicated common properties for all profiles.
Map source = ((MapPropertySource) applicationYamlPropertySource).getSource();
Properties properties = new Properties();
properties.putAll(source);
return properties;
} catch (IOException e) {
LOG.error("application.yml file cannot be found.");
}
return null;
}
#Override
protected JndiRegistry createRegistry() throws Exception {
JndiRegistry jndi = super.createRegistry();
MockitoAnnotations.initMocks(this);
jndi.bind("queueEventHandler", queueEventHandler);
return jndi;
}
#Test
// Sleeping for a few seconds is necessary, because this line template.sendBody runs in a different thread and
// CamelTest takes a few seconds to do the routing.
public void testRoute() throws InterruptedException {
template.sendBody("activemq:productpushevent", "HelloWorld!");
Thread.sleep(2000);
verify(queueEventHandler, times(1)).handleQueueEvent(any());
}
#AfterClass
public static void shutDownBroker() throws Exception {
brokerSvc.stop();
}
}
Did you try using Camel test runner?
#RunWith(CamelSpringJUnit4ClassRunner.class)
If you are using camel-spring-boot dependency, you may know that it uses auto configuration to setup Camel:
CamelAutoConfiguration.java
It means that you may also need to add #EnableAutoConfiguration to your test.

Testing Spring MVC #ExceptionHandler method with Spring MVC Test

I have the following simple controller to catch any unexpected exceptions:
#ControllerAdvice
public class ExceptionController {
#ExceptionHandler(Throwable.class)
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody
public ResponseEntity handleException(Throwable ex) {
return ResponseEntityFactory.internalServerErrorResponse("Unexpected error has occurred.", ex);
}
}
I'm trying to write an integration test using Spring MVC Test framework. This is what I have so far:
#RunWith(MockitoJUnitRunner.class)
public class ExceptionControllerTest {
private MockMvc mockMvc;
#Mock
private StatusController statusController;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(new ExceptionController(), statusController).build();
}
#Test
public void checkUnexpectedExceptionsAreCaughtAndStatusCode500IsReturnedInResponse() throws Exception {
when(statusController.checkHealth()).thenThrow(new RuntimeException("Unexpected Exception"));
mockMvc.perform(get("/api/status"))
.andDo(print())
.andExpect(status().isInternalServerError())
.andExpect(jsonPath("$.error").value("Unexpected Exception"));
}
}
I register the ExceptionController and a mock StatusController in the Spring MVC infrastructure.
In the test method I setup an expectation to throw an exception from the StatusController.
The exception is being thrown, but the ExceptionController isn't dealing with it.
I want to be able to test that the ExceptionController gets exceptions and returns an appropriate response.
Any thoughts on why this doesn't work and how I should do this kind of test?
Thanks.
I just had the same issue and the following works for me:
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(statusController)
.setControllerAdvice(new ExceptionController())
.build();
}
This code will add ability to use your exceptions controlled advice.
#Before
public void setup() {
this.mockMvc = standaloneSetup(commandsController)
.setHandlerExceptionResolvers(withExceptionControllerAdvice())
.setMessageConverters(new MappingJackson2HttpMessageConverter()).build();
}
private ExceptionHandlerExceptionResolver withExceptionControllerAdvice() {
final ExceptionHandlerExceptionResolver exceptionResolver = new ExceptionHandlerExceptionResolver() {
#Override
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(final HandlerMethod handlerMethod,
final Exception exception) {
Method method = new ExceptionHandlerMethodResolver(ExceptionController.class).resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(new ExceptionController(), method);
}
return super.getExceptionHandlerMethod(handlerMethod, exception);
}
};
exceptionResolver.afterPropertiesSet();
return exceptionResolver;
}
Since you are using stand alone setup test you need to provide exception handler manually.
mockMvc= MockMvcBuilders.standaloneSetup(adminCategoryController).setSingleView(view)
.setHandlerExceptionResolvers(getSimpleMappingExceptionResolver()).build();
I had same problem a few days back, you can see my problem and solution answered by myself here Spring MVC Controller Exception Test
Hoping my answer help you out
Use Spring MockMVC to emulate a servletContainer to a point where you can incorporate any request filtering or exception handling tests in your unit tests suite.
You can configure this setup with the following approach:
Given a custom RecordNotFound exception...
#ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Record not found") //
public class RecordNotFoundException extends RuntimeException {
private static final long serialVersionUID = 8857378116992711720L;
public RecordNotFoundException() {
super();
}
public RecordNotFoundException(String message) {
super(message);
}
}
... and a RecordNotFoundExceptionHandler
#Slf4j
#ControllerAdvice
public class BusinessExceptionHandler {
#ExceptionHandler(value = RecordNotFoundException.class)
public ResponseEntity<String> handleRecordNotFoundException(
RecordNotFoundException e,
WebRequest request) {
//Logs
LogError logging = new LogError("RecordNotFoundException",
HttpStatus.NOT_FOUND,
request.getDescription(true));
log.info(logging.toJson());
//Http error message
HttpErrorResponse response = new HttpErrorResponse(logging.getStatus(), e.getMessage());
return new ResponseEntity<>(response.toJson(),
HeaderFactory.getErrorHeaders(),
response.getStatus());
}
...
}
Configure a tailored test context: set a #ContextConfiguration to specify the classes you need for your test. Set Mockito MockMvc as a servlet container emulator and set your tests fixture and dependencies.
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {
WebConfig.class,
HeaderFactory.class,
})
#Slf4j
public class OrganisationCtrlTest {
private MockMvc mvc;
private Organisation coorg;
#MockBean
private OrganisationSvc service;
#InjectMocks
private OrganisationCtrl controller = new OrganisationCtrl();
//Constructor
public OrganisationCtrlTest() {
}
....
Configure a mock MVC "servlet emulator": register handler beans in the context and build the mockMvc emulator (Note: there are two possible configuration: standaloneSetup or webAppContextSetup; refer to the documentation). The builder rightfully implements the Builder pattern so you can chain configuration commands for exception resolvers and handlers before calling build().
#Before
public void setUp() {
final StaticApplicationContext appContext = new StaticApplicationContext();
appContext.registerBeanDefinition("BusinessExceptionHandler",
new RootBeanDefinition(BusinessExceptionHandler.class, null, null));
//InternalExceptionHandler extends ResponseEntityExceptionHandler to //handle Spring internally throwned exception
appContext.registerBeanDefinition("InternalExceptionHandler",
new RootBeanDefinition(InternalExceptionHandler.class, null,
null));
MockitoAnnotations.initMocks(this);
mvc = MockMvcBuilders.standaloneSetup(controller)
.setHandlerExceptionResolvers(getExceptionResolver(appContext))
.build();
coorg = OrganisationFixture.getFixture("orgID", "name", "webSiteUrl");
}
....
Get the exception resolver
private ExceptionHandlerExceptionResolver getExceptionResolver(
StaticApplicationContext context) {
ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
resolver.getMessageConverters().add(
new MappingJackson2HttpMessageConverter());
resolver.setApplicationContext(context);
resolver.afterPropertiesSet();
return resolver;
}
Run your tests
#Test
public void testGetSingleOrganisationRecordAnd404() throws Exception {
System.out.println("testGetSingleOrganisationRecordAndSuccess");
String request = "/orgs/{id}";
log.info("Request URL: " + request);
when(service.getOrganisation(anyString())).
thenReturn(coorg);
this.mvc.perform(get(request)
.accept("application/json")
.andExpect(content().contentType(
.APPLICATION_JSON))
.andExpect(status().notFound())
.andDo(print());
}
....
}
Hope this helps.
Jake.
Try it;
#RunWith(value = SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = { MVCConfig.class, CoreConfig.class,
PopulaterConfiguration.class })
public class ExceptionControllerTest {
private MockMvc mockMvc;
#Mock
private StatusController statusController;
#Autowired
private WebApplicationContext wac;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void checkUnexpectedExceptionsAreCaughtAndStatusCode500IsReturnedInResponse() throws Exception {
when(statusController.checkHealth()).thenThrow(new RuntimeException("Unexpected Exception"));
mockMvc.perform(get("/api/status"))
.andDo(print())
.andExpect(status().isInternalServerError())
.andExpect(jsonPath("$.error").value("Unexpected Exception"));
}
}
This is better:
((HandlerExceptionResolverComposite) wac.getBean("handlerExceptionResolver")).getExceptionResolvers().get(0)
And do not forget to scan for #ControllerAdvice beans in your #Configuration class:
#ComponentScan(basePackages = {"com.company.exception"})
...tested on Spring 4.0.2.RELEASE

Resources