How to write test so that the controller can connect to API? - java

I'm trying to deal with Rest api tests.
Well, the controller in my code connects to the external api parsing and returns in the form of JSON.
I'm trying to run a test so that it returns the result of my application’s logic.
Unfortunately, during testing I can't connect with api github.
java.lang.AssertionError: Status expected:<200> but was:<404>
Expected :200
Actual :404
#WebMvcTest(RepositoryDetailsController.class)
#ContextConfiguration(classes = RepositoryDetailsController.class)
class RepositoryDetailsControllerTestt {
#Autowired
private MockMvc mvc;
#MockBean
private RepositoryDetailsService service;
#InjectMocks
private RepositoryDetailsController repositoryDetailsController;
#Before
public void setUp() {
mvc = MockMvcBuilders.standaloneSetup(repositoryDetailsController).build();
}
#Test
public void mockTest() throws Exception {
RepositoryDetailsResponse details = new RepositoryDetailsResponse();
details.setDescription("Ruby toolkit for the GitHub API");
details.set"https://github.com/octokit/octokit.rb.git");
details.setStars("57892");
details.setName("octokit/octokit.rb");
mvc.perform(get("repository/{owner}/{repository}","octokit","octokit.rb")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(print())
.andExpect(jsonPath("$.*", hasSize(5)))
.andExpect(jsonPath("$.fullName").value(details.getName()))
.andExpect(jsonPath("$.description").value(details.getDescription()))
.andExpect(jsonPath("$.cloneUrl").value(details.getUrl()))
.andExpect(jsonPath("$.stars").value(details.getStars()));
}

If you can avoid it you usually don't want to connect to an external API in your tests. Having an external API test dependency makes life harder e.g. what if the API is down or what if the API had a breaking change?
There are few tools that you can use to mock this external API, hardcoding the expected responses while still making the request:
Wiremock - very generic, exposes a mock server that can be used with any library that makes the API call
Spring's MockRestServiceServer - especially useful if your code is using RestTemplate to make the API call
Test Containers - if you have a Docker image that provides a mock API
In your example it looks like you attempted to use MockMvc instead of MockRestServiceServer but there is not enough code to tell what you are doing exactly.

Related

How do I write an integration test for a REST API secured with OAuth2 in JUnit5?

I have a client service like this,
#Service
public class PersonClientService {
private final String EXTERNAL_API;
private RestTemplate restTemplate;
#Autowired
public PersonClientService(RestTemplate restTemplate, #Value("${person.url}") String apiUrl) {
this.restTemplate = restTemplate;
EXTERNAL_API = apiUrl
}
public ResponseDTO createData(PersonDTO personDTO) throws Exception {
try {
HttpEntity<PersonDTO> input = new HttpEntity<>(personDTO);
ResponseEntity<ResponseDTO> restponseDTO = restTemplate.exchange(EXTERNAL_API, HttpMethod.POST, input, ResponseDTO.class);
return responseDTO.getBody();
} catch(Exception e) {
//catch exception
}
}
}
Now the rest template here that I am using is secured with OAuth2 implementation and it is using client_id and secret with grant_type as client_credentials to generate a token and then using this token as header to call the EXTERNAL_API
I am following this guide here but it's not really helpful since it is using JUnit4 and I am on JUnit5: https://www.baeldung.com/oauth-api-testing-with-spring-mvc
I'm confused. What do you want to test?
The sample you link is achieving controller unit-testing with mockmvc.
They use an annotation which loads security context. As a consequence test security context must be configured for the request to reach controller endpoint.
I don't see any security rules on your service (#PreAuthorize or something) => you don't need any security context, just don't load security config.
If you add security rules you want to unit test, load security config and setup test security context (either explicitly or with something like https://github.com/ch4mpy/spring-addons/tree/master/samples/webmvc-jwtauthenticationtoken/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken)
The call to external service is a complete different story: the external service is running with a different security context than the one attached to your tested service thread). Either:
#MockBean RestTemplate (and configure mock for the Rest call your service is issuing) => unit test
ensure test configuration for RestTemplate and external service points to the same started authorization server, load rest template config, auto wire RestTemplate as normal and let it issue request for real to actual external service (which must be started too) => integration test.
You should not start with integration test. Unit test are for more stable and easier to maintain.

Spring. JUnit5. WebClient. How to test async calls

I have a Spring boot project with a service, which basically calls a private method, which does:
webClient.post().uri().accept(...).body(...).exchange()
and then puts a subscribe(...) on it, which simply logs the result.
Everything works fine, but now I need to test this, and this is where things start to get interesting.
By far, I've tried MockServer, okhttp, Spring's WebMockServer(or something), and only MockServer was willing to work at some point properly, while okhttp latest wants junit.rules.* (which is problematic to
achieve), WebMockServer specifically wants RestTemplate.
Google does give out examples, in which a webClient logic method is left without a .exchange() call, giving a chance to call .block() in the test, but I'm not willing to expose a private method just in order to workaround the async calls.
Currently I'm struggling with DEEP_STUB strategy of Mockito to mock out the actual webClient chain, but this fails to work from the box, and I'm trying to make it work while writing this question.
So the question is - is there a proper way to test a webClient with an async call (maybe a MockServer with a timeout to the verification or something)?
Wiremock seem to be an accepted way of mocking external http servers and it is now part of spring test framework.
Here is an introduction: https://www.baeldung.com/introduction-to-wiremock
Otherwise WebTestClient is a bean which might come in handy at this point if you can inject it in the constructor of your service class?
#SpringBootTest(webEnvironment = RANDOM_PORT)
#AutoConfigureWebClient
class DemoResourceTest {
#Autowired
private WebTestClient webTestClient;
#BeforeEach
public void setUp() {
webTestClient = webTestClient
.mutate()
.responseTimeout(Duration.ofMillis(1000))
.build();
}
#Test // just for reference
void some_test_that_gives_success() {
webTestClient.get()
.uri(uriBuilder -> uriBuilder
.path("/world")
.queryParam("requestString", "hello")
.build())
.accept(APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody(String.class);
}
}

What are the pros & cons of 2 types of spring testing

In my current project we are working on microservices(web app).
In unit tests we try to cover 85-90% of our code. I have noticed 2 approaches of testing using spring:
Inject a controller and invoke its methods directly
Form a proper request where you can specify cookies, headers... and then make a call
Moreover, we won't be able to test authentication with the 1 approach.
Which of the next spring testing ways should be used? And what are the (dis-)advantages of each type?
#RestController
class MyController {
#PostMapping(path="/path")
public String handle(#RequestBody MyRequest request) {
//service invoked
return "some value";
}
}
JUnit approach #1
#LotsOfAnnotations
class ControllerTest1 {
#Autowired
private MyController myController;
#Test
public String verboseNameTest() {
// Mock 3rd party calls
....
// Form request
MyRequest request = new MyRequest();
// Invoke testing method
myController.handle(request);
// Assert
}
}
JUnit approach #2
#LotsOfAnnotations
class ControllerTest2 {
#Autowired
private TestRestTemplate testTemplate;
private MockRestServiceServer server;
#Autowired
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webAppContext;
#Before
public void setup() {
server = MockRestServiceServer.createServer(testTemplate.getRestTemplate());
mockMvc = MockMvcBuilders.webAppContextSetup(this.webAppContext).build();
}
#Test
public String verboseNameTest() {
// Mock 3rd party calls
....
// Form request
String jsonStringRequest = "{}";
RequestBuilder requestBuilder = MockMvcRequestBuilders
.post("/path")
.contentType(MediaType.APPLICATION_JSON)
.content(jsonStringRequest);
// Make a call
MvcResult result = this.mockMvc
.perform(requestBuilder)
.andExpect(status().isOk())
.andReturn();
// Assert
}
}
As far as I can see you need to understand when to mock the service and when to make a direct call.
In your first approach you are directly calling the web service so let's say for example you have a huge code base and multiple developers are working at a time so you would get constant updates in the classes and if you want to test your piece of code(which is why you use JUnit) then you making direct call can result in error/ failed test case because someone might have changed something.
In second approach or mocking basically removes that possibility by mocking the other service which your code would be needing so that will make you code test easier.
But there are people who questions what's the use of mocking when you are not even testing the whole thing which is true up to certain point but the main reason for mocking is to test your piece of code only irrespective of other services that code is dependent on. Also if you talk about making actual service call that should be part of integration testing or end to end testing.

How to test RESTful web service automatically with random data

I was developing the RESTful web service with springmvc4 and spring data jpa.Well, I have about 100+ apis for frontend to pull data.What I am want to do is how to test all of my apis automatically with random data.
The apis look like:
#RestController
#Api(tags = "index")
#RequestMapping("/index")
public class IndexController {
#Autowired
private IndexService indexService;
#RequestMapping(value = "/data", method = RequestMethod.GET)
#ApiOperation(value="today's data",notes="today's data",consumes="application/json",produces="application/json")
public Object getTodayData() {
return indexService.getTodayData();
}
#RequestMapping(value = "/chartData", method = RequestMethod.GET)
#ApiOperation(value="charts data",notes="charts data",consumes="application/json",produces="application/json")
public Object getLast7Data() {
return indexService.getLast7Data();
}
}
if I test it with postman one by one,it was waste a lot of time.When we developping,we should make sure the service is ok by ourselves.
I have got a solution but which is not satisfied me well.
here are my solution:
Scaned the controller of the specified package,then use reflection
get the annotation of the class,which could get the value of
#RequestMapping("/index").
Iterate through the method of the class and get the method's
annotation the same way,and get the full url.
Create random data for request, execute request and log the response.
Could anyone provide a solution for this, very appreciate for your help.
I see that you are using swagger in your api, you can use it to generate client code https://github.com/swagger-api/swagger-codegen for automatic testing.
Since you are using the Spring framework, you can try the following :
Use Spring Integration Test for testing the API. It spawns an
instance of your service and tests against it.
Use RestAssured & JUnit to hit the API and assert the response.
Use RequestMappingHandlerMapping.getHandlerMethods(), which you can simply get with Spring injection, e.g. via #Autowired. This will give you a map RequestMappingInfo->HandlerMethod, which contains all the information you need.
You can run the tests as regular JUnit tests, without the need for postman etc. using Spring integration testing support:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextHierarchy({
#ContextConfiguration(name = "root", locations = "classpath:applicationContext.xml"),
#ContextConfiguration(name = "web", locations = "classpath:xxx-servlet.xml)
})
public class YourTest extends AbstractTransactionalJUnit4SpringContextTests {...}
In this test, use #Autowired WebApplicationContext and pass it to MockMvcBuilders.webAppContextSetup(webApplicationContext) to create a MockMvc instance. It allows to submit HTTP request to the Spring's MockMvc infrastructure via an easy interface.
Note that Spring's MockMvc framework will not run any real app server such as Tomcat. But this might be exactly what you need, since it is much faster. By default, Spring integration testing framework will only initialize your Spring application context once for all the tests with the same Spring configuration (use #DirtiesContext on a test class or method to signal that a new Spring app context is required after a specific test).
If you feel you need to run an actual app server such as Tomcat within your tests, check maven plugins such as tomcat7-maven-plugin.

Integration Tests Using Spring Boot and Security

We have a Spring application, building with Gradle, running with Spring Boot 1.2.5.RELEASE. We wrote some initial integration tests using using Rest Assured to test against our REST endpoints. This worked, and our application's REST endpoints were responding appropriately via the browser and Postman.
Then we used Spring Security to implement a OncePerRequestFilter and our own AuthenticationProvider. Our authentication is working fine, and the browser and Postman are still receiving appropriate responses, however our integration tests no longer work.
Stepping through a test, we do see our Controller endpoints being called and returning the correct output, but beyond this point we receive error (with a null stacktrack) of org.springframework.web.util.NestedServletException: Handler processing failed; nested exception is java.lang.AbstractMethodError.
We've made progress by initializing our integration tests with Spring Security, we've tried abandoning Rest Assured and just using MockMvc, we've tried going back to Rest Assured and initializing with MockMvc. No luck so far.
Our initialization code is below, the commented out portions are for Rest Assured, the current implementation is directly using MockMvc.
Any help would be greatly appreciated!
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = IamExtensionApplication.class)
#WebIntegrationTest
public class OurIntegrationTest {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
// #Autowired
// private FilterChainProxy filterChainProxy;
#Before
public void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).
apply(springSecurity()).
// addFilters(filterChainProxy).
build();
// RestAssuredMockMvc.mockMvc(mockMvc);
}
#Test
public void our_test() {
try {
ResultActions resp = amockMvc.perform(post("/our/endpoint").param("test", "test_value"));
} catch (Exception e) {
e.printStackTrace();
}
// MockMvcResponse resp = given()
// .param("test", "test_value")
// .when()
// .post("/our/endpoint");
}
We tried several variations of these configurations, but the one we finally tried that worked (that we didn't actually find documented anywhere) was to pass our filterChainProxy as a parameter to the springSecurity() function.
Hope this helps others!

Categories