Spring Boot + Cloud | Zuul Proxy | Integration testing - java

When working with Spring Boot to build micro-services its very easy to write extensive and very readable integration tests and mock remote service requests with MockRestServiceServer.
Is there a way to use similar approach to perform additional integration test on ZuulProxy? What I would like to achieve is being able to mock remote servers that ZuulProxy would forward to and validate that all of my ZuulFitlers behaved as expected. However, ZuulProxy is using RestClient from Netflix (deprecated it would seem?) which naturally does not use RestTemplate which could be re-configured by MockRestServiceServer and I currently can't find a good way of mocking responses from remote services for proxied requests.
I have a micro-service that is responsible for handling API Session Key creation and then will act similar to an API Gateway. Forwarding is done with Zuul Proxy to underlying exposed services, and Zuul Filters will detect if Session key is valid or not. An integration test would therefore create a valid session and then forward to a fake endpoint, e.g 'integration/test'.
Specifying that 'integration/test' is a new endpoint is possible by setting a configuration property on #WebIntegrationTest, I can successfully mock all services that are being handled via RestTemplate but not Zuul forwarding.
What's the best way to do achieve mocking of a forward target service?

Check out WireMock. I have been using it to do integration level testing of my Spring Cloud Zuul project.
import static com.github.tomakehurst.wiremock.client.WireMock.*;
public class TestClass {
#Rule
public WireMockRule serviceA = new WireMockRule(WireMockConfiguration.options().dynamicPort());
#Before
public void before() {
serviceA.stubFor(get(urlPathEqualTo("/test-path/test")).willReturn(aResponse()
.withHeader("Content-Type", "application/json").withStatus(200).withBody("serviceA:test-path")));
}
#Test
public void testRoute() {
ResponseEntity<String> responseEntity = this.restTemplate.getForEntity("/test-path/test", String.class);
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
serviceA.verify(1, getRequestedFor(urlPathEqualTo("/test-path/test")));
}
}

The accepted answer has the main idea. But I struggle on some points until figure out the problem. So I would like to show a more complete answer using also Wiremock.
The test:
#ActiveProfiles("test")
#TestPropertySource(locations = "classpath:/application-test.yml")
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureWireMock(port = 5001)
public class ZuulRoutesTest {
#LocalServerPort
private int port;
private TestRestTemplate restTemplate = new TestRestTemplate();
#Before
public void before() {
stubFor(get(urlPathEqualTo("/1/orders/")).willReturn(aResponse()
.withHeader("Content-Type", MediaType.TEXT_HTML_VALUE)
.withStatus(HttpStatus.OK.value())));
}
#Test
public void urlOrders() {
ResponseEntity<String> result = this.restTemplate.getForEntity("http://localhost:"+this.port +"/api/orders/", String.class);
assertEquals(HttpStatus.OK, result.getStatusCode());
verify(1, getRequestedFor(urlPathMatching("/1/.*")));
}
}
And the application-test.yml:
zuul:
prefix: /api
routes:
orders:
url: http://localhost:5001/1/
cards:
url: http://localhost:5001/2/
This should work.
But Wiremock has some limitations for me. If you has proxy requests with different hostnames running on different ports, like this:
zuul:
prefix: /api
routes:
orders:
url: http://lp-order-service:5001/
cards:
url: http://lp-card-service:5002/
A localhost Wiremock running on the same port will no be able to help you. I'm still trying to find a similar Integration Test where I could just mock a Bean from Spring and read what url the Zuul Proxy choose to route before it make the request call.

Related

Testing a Spring controller secured with Keycloak

I am trying to write a few tests for my Spring controller. The endpoints are secured with Keycloak (open id connect).
I tried mocking an authenticated user using the #WithMockUser annotation but I need to retrieve claims from the token (preferred_username) and I end up getting a null pointer exception from here:
return Long.parseLong(((KeycloakPrincipal) authentication.getPrincipal()).getKeycloakSecurityContext().getToken().getPreferredUsername());
Is there any way to mock the Keycloak token? I came across this similar question but I do not want to use the suggested external library.
Thank you guys in advance, any help would be greatly appreciated as I have been stuck on this problem for a while.
I came across this similar question but I do not want to use the suggested external library.
Well, you'd better reconsider that.
Are you using the deprecated Keycloak adapters?
If yes, and if you still don't want to use spring-addons-keycloak, you'll have to manualy populate test security context with a KeycloakAuthenticationToken instance or mock:
#Test
public void test() {
final var principal = mock(Principal.class);
when(principal.getName()).thenReturn("user");
final var account = mock(OidcKeycloakAccount.class);
when(account.getRoles()).thenReturn(Set.of("offline_access", "uma_authorization"));
when(account.getPrincipal()).thenReturn(principal);
final var authentication = mock(KeycloakAuthenticationToken.class);
when(authentication.getAccount()).thenReturn(account);
// post(...).with(authentication(authentication))
// limits to testing secured #Controller with MockMvc
// I prefer to set security context directly instead:
SecurityContextHolder.getContext().setAuthentication(authentication);
//TODO: invoque mockmvc to test #Controller or test any other type of #Component as usual
}
You'll soon understand why this #WithMockKeycloakAuth was created.
If you already migrated to something else than Keycloak adapters, solution with manualy setting test-security context still applies, just adapt the Authentication instance. If your authentication type is JwtAuthenticationToken, you can use either:
jwt() request post processor for MockMvc I wrote (it is available from spring-security-test)
#Test
void testWithPostProcessor() throws Exception {
mockMvc.perform(get("/greet").with(jwt().jwt(jwt -> {
jwt.claim("preferred_username", "Tonton Pirate");
}).authorities(List.of(new SimpleGrantedAuthority("NICE_GUY"), new SimpleGrantedAuthority("AUTHOR")))))
.andExpect(status().isOk())
.andExpect(content().string("Hi Tonton Pirate! You are granted with: [NICE_GUY, AUTHOR]."));
}
#WithMockJwtAuth, same author, different lib
#Test
#WithMockJwtAuth(authorities = { "NICE_GUY", "AUTHOR" }, claims = #OpenIdClaims(preferredUsername = "Tonton Pirate"))
void testWithPostProcessor() throws Exception {
mockMvc.perform(get("/greet"))
.andExpect(status().isOk())
.andExpect(content().string("Hi Tonton Pirate! You are granted with: [NICE_GUY, AUTHOR]."));
}
Note that only second option will work if you want to unit-test a secured #Component that is not a #Controller (a #Service or #Repository for instance).
My two cent advices:
drop Keycloak adapters now: it will disapear soon, is not adapted to boot 2.7+ (web-security config should not extend WebSecurityConfigurerAdapter any more) and is way too adherent to Keycloak. Just have a look at this tutorial to see how easy it can be to configure and unit-test a JWT resource-server (with identities issued by Keycloak or any other OIDC authorization-server)
if your team does not let you abandon Keycloak adapters yet, use #WithMockKeycloakAuth, you'll save tones of time and your test code will be way more readable.

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.

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

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.

Spring disable #EnableResourceServer

I have resource server, when it's starts - it's sending request to Authentication server ("http://localhost:xxxx/auth/oauth/token_key"), and it's okay when all up and running.
But when I testing my services I do not need this at all. How can I disable resource server or maybe I should mock something so it won't be dependent on auth server(for future security tests for controllers)?
My spring boot main:
#SpringBootApplication
#EnableEurekaClient
#EnableResourceServer
public class CalendarApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(CalendarApplication.class, args);
}
}
application.yml
security:
basic:
enabled: false
oauth2:
resource:
jwt:
keyUri: http://localhost:xxxx/auth/oauth/token_key
Test class annotations:
#RunWith(SpringJUnit4ClassRunner.class)
#WebMvcTest(value = TypeController.class, secure = false)
public class TypeControllerTest {}
Why don't you create a separate #Configuration for your #AuthenticationServer with a separate profile (#Profile("test"))? That way, you don't need to disable security and can have an in-memory Token. That's how I dealt with it. You can also disable Spring Security for your tests completely. Have a look at this question.
You can use #WithMockUser for tests
Testing Method Security
The way I've worked around this was to create a token in the database I'm using for test and to ensure that requests to my API used the token before making a request to the resource under test.
You do want your token there, since it acts as a reasonable sanity check for security. If you expect this resource to not be accessible without a specific token, then that is a useful test to have.

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.

Categories