How to test spring controller method using Junit - spring

Hi I'm new to Spring and to Junit as well. I have a method in my controller. I want to write Junit for this method (getPersons()).
#Autowired
private PersonService personService;
#RequestMapping(value="/t2/{yy_id}/person", method=RequestMethod.GET)
#ResponseBody
public PersonInfo[] getPersons() {
return personService.getPersons();
}
Can someone please help me and guide me in a right manner. Please give some example.

You should use the mvc test framework. It allows you to test all the mvc infrastructure - e.g. the #RequestMapping, #ResponseBody etc... - around your controller in addition to your own collaborators.
A very simple example of using the framework would be invoking the getPersons() method and asserting a 200 response code is received:
...
#Test
public void getPersons() throws Exception {
this.mockMvc.perform(get("/t2/1234/person"))
.andExpect(status().isOk());
}
...
The framework is capable of much more but I urge you to read the documentation, there are plenty of examples included. I hope that helps.

Related

Unit Testing Spring Boot API RESTful endpoints generated by Open API 3 YAML files

I have an application that is using Spring Boot (latest version) and creating a back-end that has RESTful api's. Traditionally, I have created controllers like:
#RestController
#RequestMapping("/contacts")
public class ContactController {
#Autowired
private ContactService service;
#RequestMapping(value = "/contactId/{contactId}",
method = RequestMethod.GET, headers = "Accept=application/json")
public #ResponseBody ContactEntity getContactById(#PathVariable("contactId") long contactId) {
ContactEntity contactEntity = service.getContactById(contactId);
return contactEntity;
}
And an integrated test has always been like:
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = ServiceContextConfiguration.class)
#ComponentScan("com.tomholmes.springboot.phonebook.server")
#Transactional
#WebAppConfiguration
public class ContactControllerTest {
#Test
public void testGetContactById() throws Exception {
MockHttpServletRequestBuilder requestBuilder =
MockMvcRequestBuilders.get(BASE_URL + "/contactId/6");
this.mockMvc.perform(requestBuilder)
.andDo(print())
.andExpect(status().isOk());
}
}
This has always worked normally for years as a 'code first' api. Now, I am dealing with a contract-first API using OpenAPI 3 and a YAML file. The API is generated in the same location as before, and I would expect the test to work as it did before, but it does not.
So one resource:
[https://www.hascode.com/2018/08/testing-openapi-swagger-schema-compliance-with-java-junit-and-assertj-swagger/#API_Test]
is suggesting I use the assertj-swagger for the OpenAPI / Swagger contract testing.
Is this the only way to go? Is there no way for me to use my old traditional testing which I find extremely useful as an integrated test.
There is a third method I am also looking into:
[https://www.testcontainers.org/modules/mockserver/]
Which I am going to try also, and I am sure it will work.
I'm also wondering if there is code out there to auto-generate the Test just like there is to generate the API endpoint and the model, it would make sense if the Open API 3 also had the ability to generate the test was well.
Ultimately, I'd like to use my old way of testing if I could, but if not, then I'll try the other ways.
We had the same issue after switching to open api contract first with auto-generated controllers/delegate interfaces. The fix was to import the delegate impl in addition to the controller itself. Example:
#WebMvcTest(FeatureController.class)
#Import(FeatureDelegateImpl.class)
public class FeatureContractTest {
#Autowired
private MockMvc mvc;
#MockBean
private BusinessService businessService;
...
FeatureController is the controller generated by open-api, which typically will be in a generated-sources folder within target. FeatureDelegateImpl is our own implementation of the delegate interface, which lives in the src/main folder.

JUnit 5 Search Rest api testing

How to test findByTargetProject method using JUnit 5.
#Repository
public interface suiteRepository extends JpaRepository<suiteInformations, Long> {
suiteInformations findByTargetProject(String url);
}
I tried this way but i am getting 404 response.
#Test
public void findByTargetProjectTest() throws Exception {
Mockito.when(suiteRepository.findByTargetProject(Mockito.anyString())).thenReturn(suiteInformation);
RequestBuilder requestBuilder = MockMvcRequestBuilders.get(
"/suiteInformationses/search/findByTargetProject?url=xyz").accept(
MediaType.APPLICATION_JSON);
MvcResult result = mockMvc.perform(requestBuilder).andReturn();
System.out.println(result.getResponse().getStatus());
}
Please note, that the #Repository you have created has no implementation at all, since logic is handled behind the curtains by Spring, so what do you want to test here using unit testing?
If you really want to make sure that the method you introduced in repository is working as expected, create an integration test and verify its behaviour on real data.

AOP advice breaks controllers

I am writing a client-server application using Spring on backend and AngularJS on frontend. After writing quite a lot of Spring code I wanted to try AOP to extract logging code from controllers' methods. Unfortunately after adding aspects to my project controllers aren't working as they should.
To describe the simplest case, there's controller:
#RestController
public class AuthenticationController {
#RequestMapping("/authenticate")
public Principal authenticate(Principal user) {
return user;
}
}
I expect the Principal to be injected here by Spring Security and sent to client - it all worked well, on the browser a response with 200 code could be seen and in the data there was authenticated Principal.
Then I introduced the following aspect:
#Aspect
#Component
public class LoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
#Pointcut("execution(* correct.package.path.controllers.impl.*Controller.*(..))")
public void withinControllers() {
};
#Pointcut("#annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void requestMapped() {
};
#Before("withinControllers() && requestMapped()")
public void logBefore(JoinPoint joinPoint) {
logger.debug("Entering {}", joinPoint.getSignature().toShortString());
}
}
The advice is executed, the authenticate method is executed, but on the browser a 404 response is visible instead of correct one from before. Eclipse logs don't show any errors and I don't know where to go from there.
I've also noticed that when commenting out the #Before advice, leaving only #Pointcuts, it works well, just like before, but it's obviously useless as I need advices to work.
Can anyone explain what's the reason for such behaviour and how can I fix that?
Regards,
imralav

Mock external server during integration testing with Spring

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.

How to write JUnit for Rest Controller

I have written Rest controller as follows:
#RestController
public class RegisterRestController {
#RequestMapping(value = "/create", method = RequestMethod.POST, consumes = "application/json", produces = "application/json")
public RegisterResponseObject createProspect(
#Valid #RequestBody ProfileBean profileBean) throws Exception {
//If validation passes do something
}
}
Here, if validation fails I am handling exception in Controller advice and sending error object back to client.
How to test this controller with JUnit for validation erros.
Thanks in advance.
Spring provides MockMvc that can perform requests. The Spring documentation is a good place to start
If you are handling all the errors using controller advice in normal client request, you can do the same in JUnit also.
While testing controller classes, we build MockMvc object and then set the custom controller advice to that. This way when ever there is any exception in the method in controller classes, it will first go to controller advice and then return to JUnit test.
For example :
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(this.customController)
.setControllerAdvice(new CustomControllerAdvice()).build();
}
This should help in testing the controller.
Note : I have tested this with Spring 4 version

Resources