I am trying to test a Spring Boot RestController that should use custom error attributes.
#Bean
public ErrorAttributes errorAttributes() {
return new DefaultErrorAttributes() {
#Override
public Map<String, Object> getErrorAttributes(
RequestAttributes requestAttributes,
boolean includeStackTrace) {
Map<String, Object> errorAttributes = super.getErrorAttributes(requestAttributes, includeStackTrace);
Throwable error = getError(requestAttributes);
return errorAttributes;
}
};
}
But when i try to test the custom error attributes using a simple test these properties are not taken into account. The test below actually fires a request and i except that the custom attributes are used. But whatever i seem to do the code seems to be not taken into account.
class TestSpec extends Specification {
MockMvc mockMvc
def setup() {
mockMvc = MockMvcBuilders.standaloneSetup(new HelloWorldController()).build()
}
def "Test simple action"() {
when:
def response = mockMvc.perform(post("/hello")
.contentType(MediaType.APPLICATION_JSON)
.content('{"sayHelloTo": ""}')
)
then:
response.andExpect(status().isOk())
}
}
Any clue on how i could test if the custom attributes?
Spring Boot's error infrastructure works by forwarding requests to an error controller. It's this error controller that uses an ErrorAttributes instance. MockMvc only had fairly basic support for testing the forwarding of requests (you can check that the request would be forwarded, but not the actual outcome of that forward). This means that a MockMvc test that calls your HellowWorldController, either using standalone setup or a web application context-based setup, isn't going to drive the right code path.
A few options:
Unit test your custom ErrorAttributes class directly
Write a MockMvc-based test that calls Spring Boot's BasicErrorController configured with your custom ErrorAttributes instance
Write an integration test that uses RestTemplate to make an actual HTTP call into your service
The test class from Spring gives you a good place to start your own tests!
Create an instance of your custom error attributes class and use MockHttpServletRequest and WebRequest:
private final DefaultErrorAttributes errorAttributes = new YourCustomErrorAttributes();
private final MockHttpServletRequest request = new MockHttpServletRequest();
private final WebRequest webRequest = new ServletWebRequest(this.request);
For your test method:
//Set the appropriate error state in your mocked request object:
RuntimeException ex = new RuntimeException("Test");
this.request.setAttribute("javax.servlet.error.exception", ex);
//Pass the mocked request into the the methods that are normally called by the framework
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, ErrorAttributeOptions.of(Include.STACK_TRACE));
// add your own asserts
assertThat(attributes.get("trace").toString()).startsWith("java.lang");
Related
My Controller calls the service to post information about a car like below and it works fine. However, my unit test fails with the IllegalArgumentException: URI is not absolute exception and none of the posts on SO were able to help with it.
Here is my controller
#RestController
#RequestMapping("/cars")
public class CarController {
#Autowired
CarService carService;
#PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<CarResponse> getCar(#RequestBody CarRequest carRequest, #RequestHeader HttpHeaders httpHeaders) {
ResponseEntity<CarResponse> carResponse = carService.getCard(carRequest, httpHeaders);
return carResponse;
}
}
Here is my service class:
#Service
public class MyServiceImpl implements MyService {
#Value("${myUri}")
private String uri;
public void setUri(String uri) { this.uri = uri; }
#Override
public ResponseEntity<CarResponse> postCar(CarRequest carRequest, HttpHeaders httpHeaders) {
List<String> authHeader = httpHeaders.get("authorization");
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", authHeader.get(0));
HttpEntity<CarRequest> request = new HttpEntity<CarRequest>(carRequest, headers);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<CarResponse> carResponse = restTemplate.postForEntity(uri, request, CarResponse.class);
return cardResponse;
}
}
However, I am having trouble getting my unit test to work. The below tests throws IllegalArgumentException: URI is not absolute exception:
public class CarServiceTest {
#InjectMocks
CarServiceImpl carServiceSut;
#Mock
RestTemplate restTemplateMock;
CardResponse cardResponseFake = new CardResponse();
#BeforeEach
void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
cardResponseFake.setCarVin(12345);
}
#Test
final void test_GetCars() {
// Arrange
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", anyString());
ResponseEntity<CarResponse> carResponseEntity = new ResponseEntity(carResponseFake, HttpStatus.OK);
String uri = "http://FAKE/URI/myapi/cars";
carServiceSut.setUri(uri);
when(restTemplateMock.postForEntity(
eq(uri),
Mockito.<HttpEntity<CarRequest>> any(),
Mockito.<Class<CarResponse>> any()))
.thenReturn(carResponseEntity);
// Act
**// NOTE: Calling this requires real uri, real authentication,
// real database which is contradicting with mocking and makes
// this an integration test rather than unit test.**
ResponseEntity<CarResponse> carResponseMock = carServiceSut.getCar(carRequestFake, headers);
// Assert
assertEquals(carResponseEntity.getBody().getCarVin(), 12345);
}
}
UPDATE 1
I figured out why the "Uri is not absolute" exection is thrown. It is because in my carService above, I use #Value to inject uri from application.properties file, but in unit tests, that is not injected.
So, I added public property to be able to set it and updated the code above, but then I found that the uri has to be a real uri to a real backend, requiring a real database.
In other words, if the uri I pass is a fake uri, the call to carServiceSut.getCar above, will fail which means this turns the test into an integration test.
This contradicts with using mocking in unit tests.
I dont want to call real backend, the restTemplateMock should be mocked and injected into carServiceSut since they are annotated as #Mock and #InjectMock respectively. Therefore, it whould stay a unit test and be isolated without need to call real backend. I have a feeling that Mockito and RestTemplate dont work well together.
You need to construct your system under test properly.
Currently, MyServiceImpl.uri is null.
More importantly, your mock of RestTemplate is not injected anywhere, and you construct a new RestTemplate in method under test.
As Mockito has no support for partial injection, you need to construct the instance manually in test.
I would:
Use constructor injection to inject both restTemplate and uri:
#Service
public class MyServiceImpl implements MyService {
private RestTemplate restTemplate;
private String uri;
public MyServiceImpl(RestTemplate restTemplate, #Value("${myUri}") uri) {
this.restTemplate = restTemplate;
this.uri = uri;
}
Construct the instance manually:
drop #Mock and #InjectMocks
drop Mockito.initMocks call
use Mockito.mock and constructor in test
public class CarServiceTest {
public static String TEST_URI = "YOUR_URI";
RestTemplate restTemplateMock = Mockito.mock(RestTemplate.class);
CarServiceImpl carServiceSut = new CarServiceImpl(restTemplateMock, TEST_URI):
}
Remove creation of restTemplate in method under test.
If needed, add a config class providing RestTemplate bean (for the application, the test does not need that):
#Configuration
public class AppConfig {
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
Note that RestTemplate is thread-safe, one instance per app is enough: Is RestTemplate thread safe?
try to change the URI as
String uri = "http://some/fake/url";
I'm using Spring 3.2.11.RELEASE and JUnit 4.11. In a particular Spring controller, I have a method that ends thusly ...
return new ModelAndView(new RedirectView(redirectUri, true));
In my JUnit test, how do I verify return from a submission to my controller in which this RedirectView is returned? I used to use org.springframework.test.web.AbstractModelAndViewTests.assertViewName, but that only returns "null", even when a non-empty ModelAndView object is returned. Here is how I'm constructing my JUnit test ...
request.setRequestURI(“/mypage/launch");
request.setMethod("POST");
…
final Object handler = handlerMapping.getHandler(request).getHandler();
final ModelAndView mav = handlerAdapter.handle(request, response, handler);
assertViewName(mav, "redirect:/landing");
Any help on how to verify that a RedirectView comes back with the proper value is appreciatd,
As Koiter said, consider moving to spring-test a and MockMvc
It providers some methods to test controllers and requests/reponses in a declarative way
you will need a #Autowired WebApplicationContext wac;
and on your #Before method setup this to use the #WebAppConfiguration of the class.
You'll end up with something
#ContextConfiguration("youconfighere.xml")
//or (classes = {YourClassConfig.class}
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
public class MyControllerTests {
#Autowired WebApplicationContext wac
private MockMvc mockMvc;
#Before
public void setup() {
//setup the mock to use the web context
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
}
Then you just need to use the MockMvcResultMatchers to assert things
#Test
public void testMyRedirect() throws Exception {
mockMvc.perform(post("you/url/")
.andExpect(status().isOk())
.andExpect(redirectUrl("you/redirect")
}
Note: post(), status() isOk() redirectUrl() are statics imports from MockMvcResultMatchers
See more what you can match here
Considering change your tool to MockMvc.
First you should create your MockMvc based on your controller.
private MockMvc mockController;
mockController =
MockMvcBuilders.standaloneSetup(loginController).setCustomArgumentResolvers(
new ServletWebArgumentResolverAdapter(new PageableArgumentResolver())).build();
After you create that object build the request with the request information. Part of this is the assert options that are contained in the API.
mockController.perform(MockMvcRequestBuilders.get(LoginControllerTest.LOGIN_CONTROLLER_URL + "?logout=true").
principal(SessionProvider.getPrincipal("GonLu004")))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.view().name("jsp/login"))
.andExpect(MockMvcResultMatchers.model().attribute("logOutMessage", logoutMessage));
The MockMvcResultMatchers contains a method for reviewing redirect information.
MockMvc from spring is a good choice to apply your unit testing on the controller layer.
I have a Spring web server that on a request makes an external call to some third-party web API (e.g. retreive Facebook oauth token). After getting data from this call it computes a response:
#RestController
public class HelloController {
#RequestMapping("/hello_to_facebook")
public String hello_to_facebook() {
// Ask facebook about something
HttpGet httpget = new HttpGet(buildURI("https", "graph.facebook.com", "/oauth/access_token"));
String response = httpClient.execute(httpget).getEntity().toString();
// .. Do something with a response
return response;
}
}
I'm writing an integration test that checks that hitting url on my server leads to some expected result. However I want to mock the external server locally so that I don't even need internet access to test all this. What is the best way to do this?
I'm a novice in spring, this is what I have so far.
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
#IntegrationTest({})
public class TestHelloControllerIT {
#Test
public void getHelloToFacebook() throws Exception {
String url = new URL("http://localhost:8080/hello_to_facebook").toString();
//Somehow setup facebook server mock ...
//FaceBookServerMock facebookMock = ...
RestTemplate template = new TestRestTemplate();
ResponseEntity<String> response = template.getForEntity(url, String.class);
assertThat(response.getBody(), equalTo("..."));
//Assert that facebook mock got called
//facebookMock.verify();
}
}
The actual real set up is more complicated - I'm making Facebook oauth login and all that logic is not in the controller but in various Spring Security objects. However I suspect that testing code is supposed to be the same since I'm just hitting urls and expect a response, isn't it?
After playing a bit with various scenarios, here is the one way how can one achieve what was asked with minimal interventions to the main code
Refactor your controller to use a parameter for thirdparty server address:
#RestController
public class HelloController {
#Value("${api_host}")
private String apiHost;
#RequestMapping("/hello_to_facebook")
public String hello_to_facebook() {
// Ask facebook about something
HttpGet httpget = new HttpGet(buildURI("http", this.apiHost, "/oauth/access_token"));
String response = httpClient.execute(httpget).getEntity().toString();
// .. Do something with a response
return response + "_PROCESSED";
}
}
'api_host' equals to 'graph.facebook.com' in application.properties in the src/main/resources
Create a new controller in the src/test/java folder that mocks the thirdparty server.
Override 'api_host' for testing to 'localhost'.
Here is the code for steps 2 and 3 in one file for brevity:
#RestController
class FacebookMockController {
#RequestMapping("/oauth/access_token")
public String oauthToken() {
return "TEST_TOKEN";
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
#IntegrationTest({"api_host=localhost",})
public class TestHelloControllerIT {
#Test
public void getHelloToFacebook() throws Exception {
String url = new URL("http://localhost:8080/hello_to_facebook").toString();
RestTemplate template = new TestRestTemplate();
ResponseEntity<String> response = template.getForEntity(url, String.class);
assertThat(response.getBody(), equalTo("TEST_TOKEN_PROCESSED"));
// Assert that facebook mock got called:
// for example add flag to mock, get the mock bean, check the flag
}
}
Is there a nicer way to do this? All feedback is appreciated!
P.S. Here are some complications I encountered putting this answer into more realistic app:
Eclipse mixes test and main configuration into classpath so you might screw up your main configuration by test classes and parameters: https://issuetracker.springsource.com/browse/STS-3882 Use gradle bootRun to avoid it
You have to open access to your mocked links in the security config if you have spring security set up. To append to a security config instead of messing with a main configuration config:
#Configuration
#Order(1)
class TestWebSecurityConfig extends WebSecurityConfig {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/oauth/access_token").permitAll();
super.configure(http);
}
}
It is not straightforward to hit https links in integration tests. I end up using TestRestTemplate with custom request factory and configured SSLConnectionSocketFactory.
If you use RestTemplate inside the HelloController you would be able to test it MockRestServiceTest, like here: https://www.baeldung.com/spring-mock-rest-template#using-spring-test
In this case
#RunWith(SpringJUnit4ClassRunner.class)
// Importand we need a working environment
#SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TestHelloControllerIT {
#Autowired
private RestTemplate restTemplate;
// Available by default in SpringBootTest env
#Autowired
private TestRestTemplate testRestTemplate;
#Value("${api_host}")
private String apiHost;
private MockRestServiceServer mockServer;
#Before
public void init(){
mockServer = MockRestServiceServer.createServer(this.restTemplate);
}
#Test
public void getHelloToFacebook() throws Exception {
mockServer.expect(ExpectedCount.manyTimes(),
requestTo(buildURI("http", this.apiHost, "/oauth/access_token"))))
.andExpect(method(HttpMethod.POST))
.andRespond(withStatus(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body("{\"token\": \"TEST_TOKEN\"}")
);
// You can use relative URI thanks to TestRestTemplate
ResponseEntity<String> response = testRestTemplate.getForEntity("/hello_to_facebook", String.class);
// Do the test you need
}
}
Remember that you need a common RestTemplateConfiguration for autowiring, like this:
#Configuration
public class RestTemplateConfiguration {
/**
* A RestTemplate that compresses requests.
*
* #return RestTemplate
*/
#Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
And that you have to use it inside HelloController as well
#RestController
public class HelloController {
#Autowired
private RestTemplate restTemplate;
#RequestMapping("/hello_to_facebook")
public String hello_to_facebook() {
String response = restTemplate.getForEntity(buildURI("https", "graph.facebook.com", "/oauth/access_token"), String.class).getBody();
// .. Do something with a response
return response;
}
}
2018 Things have improved much.
I ended up using spring-cloud-contracts
Here's a video introduction https://www.youtube.com/watch?v=JEmpIDiX7LU . The first part of the talk walk you through a legacy service. That's the one you can use for external API.
Gist is,
You create a Contract for the external service using Groovy DSL or other methods that even support explicit calls/proxy or recording. Check documentation on what works for you
Since you dont actually have control over the 3rd party in this case, you will use the contract-verifier and create the stub locally but remember to skipTests
With the stub-jar now compiled and available you can run it from within your test cases as it will run a Wiremock for you.
This question and several stackoverflow answers helped me find the solution so here is my sample project for the next person who has these and other similar microservices related tests.
https://github.com/abshkd/spring-cloud-sample-games
With everything working once you will never ever look back and do all your tests with spring-cloud-contracts
#marcin-grzejszczak the author, is also on SO and he helped a lot figure this out. so if you get stuck, just post on SO.
You could have another spring configuration file that exposes the same endpoint as the HelloController class. You could then simply return the canned json response.
From your code, I'm not sure about just what you are trying to accomplish. If you simply want to see that the call to facebook works then there's no substitute for testing against the service that actually talks to facebook. Mocking the facebook response just to ensure that it is mocked correctly, doesn't strike me as a terribly useful test.
If you are testing to see that the data that comes back from facebook is mutated in some way and you want to make sure that the work being done on it is correct, then you could do that work in a separate method that took the facebook response as a paramater, and then carried out the mutation. You could then check based on various json inputs that it was working correctly.
You could test without bringing the web service into it at all.
As part of Spring 3 MVC it is possible to inject the currently logged in user (Principle) object into a controller method.
E.g.
#Controller
public class MyController {
#RequestMapping(value="/update", method = RequestMethod.POST)
public String update(ModelMap model, Principal principal) {
String name = principal.getName();
... the rest here
}
}
This is documented as part of the Spring 3 documentation here:
http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html#mvc-ann-requestmapping-arguments.
This works in the production code. However I don't know how to test this.
When I create an integration test (having set up spring security context as well)
and call the controller handle method then the Principal is always null!
public class FareTypeControllerIntegrationTest extends SpringTestBase {
#Autowired
private MyController controller;
#Autowired
private AnnotationMethodHandlerAdapter handlerAdapter;
private final MockHttpServletRequest request = new MockHttpServletRequest();
private final MockHttpServletResponse response = new MockHttpServletResponse();
#Test
public void testUpdate() throws Exception {
request.setRequestURI("/update");
request.setMethod(HttpMethod.POST.name());
... setup rest of request
ModelAndView mav = handlerAdapter.handle(request, response, controller);
.. rest of assertions
}
The tests are running correctly and everything except the Principal is null.
Any ideas?
TIA
Ayub
After a quick look into Spring sources this should work:
request.setUserPrincipal(somePrincipal);
I've tried to do this some time ago, here is the method i used to set up authentication.
protected void setSecurityContext(String login){
userDetailsTest = userManager.loadUserByUsername(login);
TestingAuthenticationToken testingAuthenticationToken = new TestingAuthenticationToken(userDetailsTest, userDetailsTest.getAuthorities());
SecurityContext securityContext = new SecurityContextImpl();
securityContext.setAuthentication((Authentication) testingAuthenticationToken);
SecurityContextHolder.setContext(securityContext);
}
Then i just call it in the #Before method of the test.
Hope it helps.
I do something like this in my tests prior to calling code using Spring Security (such as the Principal parameter resolver you are testing):
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("wiseau", "Love is blind"));
I am trying to test my application with junit.
Therefore I've setup the following class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "/META-INF/spring/applicationContext-test.xml" )
#TransactionConfiguration
#Transactional
public class DispatcherServletTest extends AbstractJUnit4SpringContextTests {
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private DispatcherServlet dispatcher;
#Before
public void setUp() throws Exception {
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
MockServletConfig config = new MockServletConfig("myapp");
config.addInitParameter("contextConfigLocation","classpath*:webmvc-config.xml");
dispatcher = new DispatcherServlet();
dispatcher.init(config);
}
//test cases
}
So the problem is, that it seems that my dispatcher servlet cannot send any request to any of my controllers.
I think that there is something with the configuration - contextConfigurationLocation.
It looks like he can find the file (otherwise it would throw an exception) , but doesn't load any configuration
The logger says:
org.springframework.web.servlet.PageNotFound - No mapping found for HTTP request with URI [http://localhost:8080/myapp/abc]
But I have absolutely no idea what's wrong...
I would appreciate any help!
Thanks in advance
Mines are working fine, try the following tweaks.
if you're using Junit4 no need to extend you test class, the junit runner should do the trick
Load the context config via classpath, and make sure is accessible from the test classpath
#ContextConfiguration(locations={"classpath:applicationContext-test.xml"})
then just test the annotated controllers. I do it like this:
#Test
#Transactional
public void testAnnotatedListUser() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
AnnotationMethodHandlerAdapter handlerAdpt = new AnnotationMethodHandlerAdapter();
request.setRequestURI("/you/URIhere");
ModelAndView mav = handlerAdpt.handle(request, response, this.controller);
assertEquals("Incorrect view name returned", "myexpectedviewname", mav.getViewName());
}
There are several Problems in my question:
At first, it is not possible to extend the AbstractJUnit4SpringContextTests and use #RunWith(...), because it's the same.
At second, you should not use the dispatcherServlert, but an Handler by defining the handler in you application.xml and autowiring it in the test case via #Autowire private Handler handler...
Then everything should work fine!