Re-direct requests to SideEffect Utility Classes - java

for a spring boot application that needs to be tested below is my query.
#CustomLog
#RestController
#RequestMapping("/my_path")
public class MyController {
#GetMapping(path = "**", produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<JsonNode> fetchData(HttpServletRequest request){
... some code.....which also calls external apis.....
}
#PostMapping(path = "**", produces = {MediaType.APPLICATION_JSON_VALUE})
#ResponseBody
public ResponseEntity<Map<String, Object>> createMOI(HttpServletRequest request){
... some code.....which also calls external apis.....
}
}
My application calls an external service which now needs to be mocked.
this.webClient = WebClient.builder().baseUrl("http://localhost:9600/external_host_path")
.defaultHeader(HttpHeaders.CONTENT_TYPE,MediaType.APPLICATION_JSON_VALUE)
.build();
Mono<Pojo>responseMo = webClient.post().uri("/aGivenSubPath")
.accept(MediaType.APPLICATION_JSON).bodyValue(requestPoJo)
.retrieve().bodyToMono(Pojo.class).block();
I am calling my controller API with MVC as part of springtest
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyControllerTest {
#Autowired
MyController controller;
#Before
public void setup() throws Exception {
this.mockMvc = standaloneSetup(this.controller).build();
}
#Test
public void testControl() throws Exception {
mockMvc
.perform(post("http://localhost:9600/my_path")
.contentType(MediaType.APPLICATION_JSON)
.content("{'someData':'[]'}"))
.andExpect(status().isAccepted())
.andReturn();
}
}
What I am looking for is to somehow proxy or side effect
http://localhost:9600/external_host_path
and redirect all calls made for this host to a custom Utility class which provides response based on the request params to the external host programatically.
I have seen multiple examples for mockito, wireMock, mockwebserver, mockserver etc
But most of them work on a given(static path)-when(static path called)-then(give static response).
I have many calls through out the flow and I already have the logic of the utility class to generate responses for provided request arguments.

Although I was not able to find a answer to redirect webserver request to sideEffect class,
For now atleast managing by Mockito's MockBean and Answer.
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyControllerTest {
#Autowired
MyController controller;
#MockBean
MyExternalServiceClient serviceClient;
#Autowired
MySideEffectService sideEffect;
#Before
public void setup() throws Exception {
this.mockMvc = standaloneSetup(this.controller).build();
Mockito.when(serviceClient.getMethod(any(),anyBoolean())).thenAnswer((Answer) invocation -> {
Object[] args = invocation.getArguments();
Object mock = invocation.getMock();
return sideEffect.getMethod((Map<String, List<String>>) args[0], (Boolean) args[1]);
});
}
#Test
public void testControl() throws Exception {
mockMvc
.perform(post("http://localhost:9600/my_path")
.contentType(MediaType.APPLICATION_JSON)
.content("{'someData':'[]'}"))
.andExpect(status().isAccepted())
.andReturn();
}
}
Will still have a lookout for a way (Maybe TestContainers with image creation on the fly that will create a server with my mockCode, so that i can use hostname of this one and replace with existing real hostname)

Related

Spring Boot controller test loading entire application context

Spring Boot here. I currently have the following REST controller:
#RestController
public class FizzbuzzController {
private final FizzbuzzService FizzbuzzService;
public FizzbuzzController(FizzbuzzService FizzbuzzService) {
this.FizzbuzzService = FizzbuzzService;
}
#PostMapping("/Fizzbuzzs/{fizzbuzzId}")
public ResponseEntity<FizzbuzzDTO> addFizzbuzz(#RequestParam("files") List<MultipartFile> files,
#PathVariable String fizzbuzzId) throws IOException {
FizzbuzzDTO fizzbuzzDTO = fizzbuzzService.store(files, fizzbuzzId);
return ResponseEntity.status(HttpStatus.OK).body(fizzbuzzDTO);
}
}
I would like to write an integration test for it that:
Mocks or stubs an HTTP request to the URL; and
Allows me to inject the FizzbuzzController (under test) with a mocked FizzbuzzService or the real thing; and
Allows me to inspect the HTTP response coming back from the method (check status code, check response entity, etc.)
My best attempt thus far:
#WebMvcTest(FizzbuzzController.class)
#EnableConfigurationProperties
#AutoConfigureMockMvc
public class FizzbuzzControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private FizzbuzzService FizzbuzzService;
#Test
public void should_store_fizzbuzz_files() throws Exception {
// I can't even get the test to run
assertTrue(1 == 1);
}
}
When I run this, the test fails to run and it is clear (looking at the logs) that Spring is loading the entire application context of my app, whereas I just want it to isolate the context to this test class, the main FizzbuzzController class, and anything in the dependency tree underneath it.
Can anyone spot where I'm going awry?
You need another context for testing. I'm suggesting you to have a separate Test config:
#TestConfiguration
#Slf4j
#EnableJpaRepositories("tth.patientportal.repository")
public class TestConfig { // bean configs goes here for testing if you need to change
// context}
and in a controller test build the context like below:
#RunWith(SpringRunner.class)
#AutoConfigureTestEntityManager
#SpringBootTest
#TestPropertySource("classpath:application-unittest.properties")
#ContextConfiguration(classes = {TestConfig.class})
public class RestControllerTest {
#Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
#Before
public void setup()
{
mockMvc = MockMvcBuilders.
webAppContextSetup(webApplicationContext)
.build();
}
#Test
public void shouldReturnRegisteredUser() throws Exception {
this.mockMvc.
perform(MockMvcRequestBuilders
.post("url")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.username").exists());
}
}

Spring boot WireMock junit5 not mocking an external call

My FristService is internally calling a SecondService through feign client, and I am trying to write a test for FirstService and want to mock the call to the second service.
It seems like wiremock is unable to intercept and respond with the mock results but throws the following exception (as it's not mocking)
java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: second-service
at org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient.execute(LoadBalancerFeignClient.java:90)
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:119)
Here is the Test code
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ContextConfiguration(initializers = {WireMockInitializer.class})
#AutoConfigureMockMvc
public class TestFirstController {
#Autowired
private WireMockServer wireMockServer;
#Inject
public MockMvc mockMvc;
#LocalServerPort
private Integer port;
#AfterEach
public void afterEach() {
this.wireMockServer.resetAll();
}
#Test
public void testSomeMethod() throws Exception {
this.wireMockServer.stubFor(
WireMock.get(urlPathMatching("/second-controller/1"))
.willReturn(aResponse()
.withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE)
.withBody("[{\"id\":1,\"message\":\"Child title 1\"}]"))
);
mockMvc.perform(MockMvcRequestBuilders.request(HttpMethod.GET, "/first-controller/1"))
.andDo(MockMvcResultHandlers.print())
.andExpect(status().isOk());
}
}
here is my business method that first fetches record from the database and then makes a call to the second service
#Override
public First getFirst(Integer id) {
First first = map(respository.findById(id));
//feign-client call
List<Second> seconds = client.getSeconds(id);
first.setChild(seconds);
return first;
}
and here is my feign client and I am not changing anything about it in test mode
#FeignClient("service-2")
public interface SecondApi {
#GetMapping({"/second-controller/{id}"})
SomeData getData(#PathVariable("id") Integer id);
}

Unable to mock ReactriveJWTDecoder

Given I have the following custom implementation of ReactiveJwtDecoder:
#Component
public class CustomReactiveJWTDecoder implements ReactiveJwtDecoder
And I write a test like:
#RunWith(SpringRunner.class)
#SpringBootTest
public class AzureADConfigTest {
#Autowired
private ApplicationContext context;
private WebTestClient client;
#MockBean
AzureADService azureADService;
#MockBean
CustomReactiveJWTDecoder decoder;
#Before
public void setUp() {
Jwt jwt = createJwt();
UsernamePasswordAuthenticationToken user = new UsernamePasswordAuthenticationToken(new User("test#user.se", "", AuthorityUtils.commaSeparatedStringToAuthorityList("A_AD")), "", AuthorityUtils.commaSeparatedStringToAuthorityList("A_AD"));
when(decoder.decode(anyString())).thenReturn(Mono.just(jwt));
when(azureADService.getUserInfo(any())).thenReturn(Mono.empty());
client = WebTestClient.bindToApplicationContext(context).build();
}
#Test
public void azureAdAccessGrated() {
client
.get()
.uri("/api/userinfo")
.header("Authorization", "Bearer " + "token")
.exchange()
.expectStatus()
.isOk();
}
}
The mock is not respected. If I put a breakpoint inside my original impl that code gets executed instead of the mock.
My question is:
How can I mock a ReactiveJWTDecoder when im using a Spring Boot Security Resource Server? There are many suggestions on how to do it but none that I as simple as just creating a #MockBean.

How to Junit a RestController But with Spring Mobile (spring-mobile-device)

I have a Rest controller with a Device (Device must be resolvem, I'm using spring-mobile-device) as a Parameter. The unit test gave me a status 415.
Here is the Code of
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> authenticationRequest(#RequestBody AuthenticationRequestDto authenticationRequest,
Device device) throws AuthenticationException {
Authentication authentication = this.authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
authenticationRequest.getUsername(), authenticationRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetails userDetails = this.userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
String token = this.tokenGenerator.generateToken(userDetails, device);
return ResponseEntity.ok(new AuthenticationResponseDto(token));
}
Unit test
ResultActions res = mockMvc.perform(post("/auth", authentication, device).contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonBytes(authentication)));
res.andExpect(status().isOk());
Well basically I was wrong with my configuration. It is mandatory configure the Web Config for testing in same way that production configuration but are grammatically different. Well I learned a lot about MockMVC config with this problem.
Here's the solution if you want do unit testing with spring mobile.
First Class
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {WebTestConfig.class})
#WebAppConfiguration
public class WebTestConfigAware {
#Autowired
private WebApplicationContext context;
protected MockMvc mockMvc;
#Autowired
private FilterChainProxy springSecurityFilterChain;
#Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
DeviceResolverRequestFilter deviceResolverRequestFilter = new DeviceResolverRequestFilter();
mockMvc = MockMvcBuilders.webAppContextSetup(context)
.addFilters(this.springSecurityFilterChain, deviceResolverRequestFilter).build();
}
}
Second class
#Configuration
#EnableWebMvc
#Import({RootTestConfig.class, WebCommonSecurityConfig.class})
public class WebTestConfig extends WebMvcConfigurerAdapter{
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new ServletWebArgumentResolverAdapter(new DeviceWebArgumentResolver()));
argumentResolvers.add(new SitePreferenceHandlerMethodArgumentResolver());
}
}
and Test Class
public class AuthenticationControllerTest extends WebTestConfigAware {
#Test
public void testAuthenticationRequest() throws Exception {
AuthenticationRequestDto authentication = new AuthenticationRequestDto();
authentication.setUsername("admin");
authentication.setPassword("Test1234");
String jsonAuthentication = TestUtil.convertObjectToJsonString(authentication);
ResultActions res = mockMvc.perform(post("/auth")
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE).content(jsonAuthentication));
res.andExpect(status().isOk());
}
In your test class you are improperly constructing your request
// a couple of issues here explained below
ResultActions res = mockMvc.perform(post("/auth", authentication, device).contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonBytes(authentication)));
post("/auth", authentication, device) authentication and device are interpreted as path URI so they are not needed here, your controller URI does not have any path URI variables.
If your intent is to pass 2 objects as the body of the request then you need to modify your test request and your controller request handler. You cannot pass 2 objects as the body of a request, you need to encapsulate both objects in one like
class AuthenticationRequest {
private AuthenticationRequestDto authenticationDto;
private Device device;
// constructor, getters and setters
}
In your controller
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> authenticationRequest(#RequestBody AuthenticationRequest request) throws AuthenticationException {
AuthenticationRequestDto authenticationDto = request.getAuthenticationDto();
Device device = request.getDevice();
// ....
}
Also in you test you need to pass a JSON object string, you are converting it to bytes (this is why you are getting a 415):
// note the change in the TestUtils, the method being called is convertObjectToJsonString (you'll need to add it)
ResultActions res = mockMvc.perform(post("/auth").contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonString(new Authenticationrequest(authentication, device))));

Spring MVC Testing: Controller Method Parameters

I am trying to write tests for my Spring MVC web application.
I have successfully configured a MockMvc object and can execute preform() operations, and can verify that my controller methods are being called.
The issue I am experiencing has to do with passing in a UserDetails object to my controller methods.
My controller method signature is as follows:
#RequestMapping(method = RequestMethod.GET)
public ModelAndView ticketsLanding(
#AuthenticationPrincipal CustomUserDetails user) {
...
}
During the tests, user is null (which is causing a NullPointerException due to my code.
Here is my test method:
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
#Test
public void ticketsLanding() throws Exception {
// testUser is populated in the #Before method
this.mockMvc.perform(
get("/tickets").with(user(testUser))).andExpect(
model().attributeExists("tickets"));
}
So my question is how do I properly pass a UserDetails object into my MockMvc controllers? What about other, non-security related objects such as form dtos?
Thanks for your help.
You need to init the security context in your unit test like so:
#Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
I use the following setup:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(locations = {
"classpath:/spring/root-test-context.xml"})
public class UserAppTest implements InitializingBean{
#Autowired
WebApplicationContext wac;
#Autowired
private FilterChainProxy springSecurityFilterChain;
// other test methods...
#Override
public void afterPropertiesSet() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(wac)
.addFilters(springSecurityFilterChain)
.build();
}
}

Categories

Resources