I want to mock an external API, which I am calling as part of my service. Therefore, I wanted to use the MockWebServer from okhttp3. My problem is that the call to bodyToMono works fine, if I want to retrieve the body as string, but does not work when retrieving it as data class. I tried to trim it down using the following code snippet:
public class MockWebClientTest {
private MockWebServer server;
#BeforeEach
public void setUp() throws IOException {
server = new MockWebServer();
server.start(9876);
}
#AfterEach
public void tearDown() throws IOException {
server.shutdown();
}
#Test
public void stringWorks() throws JsonProcessingException {
createMockedTokenCall();
Mono<String> response = WebClient.create(this.server.url("/").toString())
.get()
.uri("/")
.retrieve()
.bodyToMono(String.class);
System.out.println(response.block());
}
#Test
public void classDoesNotWork() {
createMockedTokenCall();
Mono<AuthToken> response = WebClient.create(this.server.url("/").toString())
.get()
.uri("/")
.retrieve()
.bodyToMono(AuthToken.class);
System.out.println(response.block());
}
private void createMockedTokenCall() {
server.enqueue(new MockResponse().setBody("{\"accessToken\":\"BARBAR\",\"refreshToken\":\"FOOFOO\"}"));
}
}
class AuthToken {
private String accessToken;
private String refreshToken;
//constructor
}
The first Test (stringWorks) is working fine and return the correct json representation. However, the second test (classDoesNotWork) hangs forever on the bodyToMono call.
My guess is that it has nothing to do with the okttp3 library directly, since I had the same error using Wiremock. The same code works however when targeting a real API endpoint. Unfortunately, I could not find another way to test my calls using WebClient since Spring currently has no direct mocking support for it (see SPR-15286).
I am really looking forward to help on that matter! Thanks in advance!
General remark: This is basically a more or less copy of test case shouldReceiveJsonAsPojo in https://github.com/spring-projects/spring-framework/blob/master/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java
Ok, after looking at the linked test and doing a bit of comparison, I found the solution (or my bug so to say): I forgot the correct Content-Type header. So it works using the following:
private void createMockedTokenCall() {
server.enqueue(new MockResponse().setHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE).setBody("{\"accessToken\":\"BARBAR\",\"refreshToken\":\"FOOFOO\"}"));
}
Apart from me doing a mistake, I really think that this should not result in an infinite hanging of the application...
Related
So I am trying to make a test for the post method of my controller, but I keep finding tests from other people that do not work on my or their post methods are way more advanced.
My post method
#Autowired
PartyLeaderService partyleaderService;
#PostMapping("/")
public void add(#RequestBody PartyLeaderDto partyLeaderDto){
partyLeaderService.savePartyLeader(partyLeaderDto);
}
As you can see it is fairly simple, but I still can not seem to get it to work properly.
I tried this method:
#Test
public void testPostExample() {
PartyLeaderDto partyLeaderDto = new PartyLeaderDto(1, "arun", "link");
partyLeaderController.add(partyLeaderDto);
Mockito.verify(partyLeaderController).add(partyLeaderDto);
}
But SonarQube is saying that the method is still not covered by tests, so my test must be wrong.
My DTO
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Setter
#Builder
public class PartyLeaderDto {
private Integer id;
private String name;
private String apperance;
}
Can anyone help me, because I think the answer is fairly simple, but I can't seem to find it.
The issue with your unit test is that you're verifying a call over something that is not a mock
Mockito.verify(partyLeaderController).add(partyLeaderDto);
partyLeaderController is not a mock as this is what you're looking to test at the moment.
What you should be mocking and verifying is PartyService.
#Test
public void testPostExample() {
PartyService partyService = mock(PartyService.class);
PartyLeaderController controller = new PartyLeaderController(partyService);
PartyLeaderDto partyLeaderDto = new PartyLeaderDto(1, "arun", "link");
controller.add(partyLeaderDto);
Mockito.verify(partyService).saveParty(partyLeaderDto);
}
However this is not a very good approach to test the web layer, as what you should be looking to cover with your test is not only whether PartyService is called but also that you're exposing an endpoint in a given path, this endpoint is expecting the POST method, it's expecting an object (as json perhaps?) and it's returning a status code (and maybe a response object?)
An easy, unit test-like approach to cover this is to use MockMvc.
#Test
public void testPostExample() {
PartyService partyService = mock(PartyService.class);
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new PartyLeaderController(partyService)).build();
PartyLeaderDto partyLeaderDto = new PartyLeaderDto(1, "arun", "link");
mockMvc.perform(
post("/party")
.contentType(MediaType.APPLICATION_JSON)
.content(new ObjectMapper().writeValueAsString(partyLeaderDto))
).andExpect(status().isOk());
verify(partyService).saveParty(partyLeaderDto);
}
What you are doing here is actually a unit test by verifying a stub call.
Either you can unit test by mocking the behaviour using Mockito's when(....).then(...) and then using assertion to assert the results.
or
You can follow this article to use a MockMvc to do an integration test step by step-
https://www.baeldung.com/integration-testing-in-spring
With the help of Yayotron I got to the answer.
This is the code:
#Test
#WithMockUser("Test User")
public void testPostParty() throws Exception {
PartyLeaderService partyLeaderService = mock(PartyLeaderService.class);
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new PartyLeaderController(partyLeaderService)).build();
PartyLeaderDto partyLeaderDto = new PartyLeaderDto(1, "arun", "link");
mockMvc.perform(
MockMvcRequestBuilders.post("http://localhost:8080/partyleaders/")
.contentType(MediaType.APPLICATION_JSON)
.content(new ObjectMapper().writeValueAsString(partyLeaderDto))
).andExpect(status().isOk());
Mockito.verify(partyLeaderService).savePartyLeader(ArgumentMatchers.refEq(partyLeaderDto));
}
Thanks a lot for helping!
I have a spring based project and I am trying to improve the code coverage within it
I have the following block of code which uses a lambda on the defferedResult onCompletion method
util.getResponse(userInfoDeferredResult, url, userName, password);
userInfoDeferredResult.onCompletion(() -> {
//the result can be a String or ErrorResponse
//if ErrorResponse we don't want to cause ClassCastException and we don't need to cache the result
if (userInfoDeferredResult.getResult() instanceof String){
String response = (String) userInfoDeferredResult.getResult();
cacheServices.addValueToCache(Service.USER_INFO_CACHE_NAME, corpId, response);
}
});
I was wondering - is it possible to mock the contents of the onCompletion lambda using mockito or powerMockito?
Extract contents to new method:
if(userInfoDeferredResult.getResult() instanceof String) {
String response = (String) userInfoDeferredResult.getResult();
cacheServices.addValueToCache(Service.USER_INFO_CACHE_NAME, corpId, response);
}
Then test the method that way?
Your test should rather mock cacheServices, and execute the lambda.
Extracting contents to new method is good solution in this case as mentioned in other answer.
Also, you can find article about in the following link:
http://radar.oreilly.com/2014/12/unit-testing-java-8-lambda-expressions-and-streams.html
Generally I prefer not to change the service code for test code (for example extracting to method and make it public although it should be private). The onCompletion method is triggered when the completed of AsyncContext is called. So you can test in the following way:
#RunWith(SpringRunner.class)
#WebMvcTest(DeferredResultController.class)
public class DeferredResultControllerUnitTest {
#MockBean
CacheServices cacheServices;
#Autowired
private MockMvc mockMvc;
#Test
public void onCompletionTest() throws Exception {
mockMvc.perform(get("/your_url"))
.andDo(mvcResult -> mvcResult.getRequest().getAsyncContext().complete());
Mockito.verify(cacheServices)
.addValueToCache(Service.USER_INFO_CACHE_NAME, getExpextedCorpId(), getExpectedResponse());
}
}
Working github example here.
In contrast to #SpringBootTest, #WebMvcTest won't start the all Spring application context so it is more lighter.
I have the following problem: I'm using Spring-Boot for a little private web-based project and I want Spring to make a call to a webservice when it's started. And by started I mean "when my application is ready to handle requests".
I've already tried implementing the ApplicationListener<ContextRefreshedEvent> but it did not work, as the Event happend to early (i.e. before the embedded server was ready to handle request). Also the options mentioned in this question did not solve this problem.
My question now is: Is there any possibilty to tell Spring to execute something after the server has finished starting up and is ready to handle requests?
EDIT (in response to Daniel's answer):
The problem is that I need some injected properties to make that webservice call, and since injecting static values does not work in spring this approach is no option.
My listener, that does what I want, just a bit too early looks something like this:
#Component
public class StartupListener implements ApplicationListener{
#Autowired
private URLProvider urlProvider;
#Value("${server.port}")
private int port;
#Value("${project.name}")
private String projectName;
#Override
public final void onApplicationEvent(ContextRefreshedEvent event) {
RestTemplate template = new RestTemplate();
String url = uriProvider.getWebserviceUrl(this.projectName);
template.put(url, null);
}
}
SECOND EDIT:
Although this question solves a very similar problem it seems like I'm not able to inject into the object because it needs to have a constructor of the form (org.springframework.boot.SpringApplication, [Ljava.lang.String;).
Also it would be desirebale to solve it without having to create the spring.factories file but by using annotations.
If I understand what your problem is, you could call the webservice on your application main, right after it initiates.
public static void main(String[] args) {
new SpringApplication(Application.class).run(args);
//call the webservice for you to handle...
}
I'm not sure if this is what you want...
In your component you can use the #PostConstruct annotation. e.g.
#Component
public class StartupListener {
#Autowired
private URLProvider urlProvider;
#Value("${server.port}")
private int port;
#Value("${project.name}")
private String projectName;
#PostConstruct
public final void init() {
RestTemplate template = new RestTemplate();
String url = uriProvider.getWebserviceUrl(this.projectName);
template.put(url, null);
}
}
This will fire once the bean has been initialised and autowiring has taken place.
#Component
public class StartUp implements ApplicationListener<WebServerInitializedEvent> {
private WebClient webClient;
#Override
public void onApplicationEvent(WebServerInitializedEvent event) {
String baseUrl = "http://url.com"
webClient = WebClient.create(baseUrl);
executeRestCall(baseUrl+"/info");
}
void executeRestCall(String uri) {
try {
webClient.get()
.uri(uri)
.exchange()
.block();
} catch (Exception e) {
log.error("Request failed for url - {}",uri, e);
}
}}
In my integration test, I tried to use resttemplate to send a Get request to a dummy server created by MockMvcBuilders. However I got an error:
I/O error on GET request for "http://localhost:8080/test":Connection refused:
(In the function testAccess(), url is "http://localhost:8080/test"). My code is as below:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#IntegrationTest("server.port=8080")
public class MyTest {
private MockMvc mockMvc = null;
#Autowired
private WebApplicationContext context;
#Value("${server.port}")
private int port;
#Autowired
private MyController myController;
#Before
public void setUp(){
mockMvc = MockMvcBuilders.webAppContextSetup(context)
.build();
}
#Test
public void testAccess() throws Exception{
RestTemplate restTemplate=new RestTemplate();
String url="http://localhost:8080/test";
try{
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, null, String.class);
}
catch(ResourceAccessException ex){
System.out.println(ex.getMessage());
}
}
#Controller
public static class MyController {
#RequestMapping(value = "/test", method = RequestMethod.GET)
public #ResponseBody String access() {
return "OK";
}
}
}
The way I've done it is this:
First, you create a mock server from the actual RestTemplate you are using in your application
#Before
public void setup() throws Exception {
mockServer = MockRestServiceServer.createServer(myService.restTemplate);
}
Then you define how that request is going to work:
mockServer.expect(requestTo("http://localhost:8080/myrestapi"))
.andExpect(method(HttpMethod.POST))
.andRespond(withSuccess("{ success: true }", MediaType.APPLICATION_JSON));
And last you call the method in your application that will trigger a call to that url with that RestTemplate:
#Test
public void testThis() throws Exception {
myService.somethingThatCallsMyRestApi(parameters);
}
That will make your tests work as if there was a server up and running to process requests.
Using this in your example makes no sense, cause you would be testing that you build your test correctly and nothing else from the actual application.
The problem with this is that you cannot test dynamic responses. I mean, in my case the method I'm calling generates different data every time you call it and then sends it to the mockServer and then validates that the response matches in some very specific way. I haven't found a solution yet, but if the data you are going to send and receive is previously known, which would be in most cases, you'll have no problem using this.
Why are you defining a controller in your Test class and then trying to test it ? It doesn't feel logical to try to test something that is defined within the test it self.
Rather you would want to test a controller defined somewhere outside your tests, an actual controller that is used within your application.
Let's say MyController is defined as an actual controller then you could use the mockMvc object you created to test it.
mockMvc.perform(get('/test'))
.andExpect(status().isOk())
In my jersey-2 application I'm using a very simple ContainerRequestFilter that will check for basic authentication (probably reinventing the wheel, but bear with me). Filter goes somewhat like this
#Override
public void filter(ContainerRequestContext context) throws IOException {
String authHeader = context.getHeaderString(HttpHeaders.AUTHORIZATION);
if (StringUtils.isBlank(authHeader)) {
log.info("Auth header is missing.");
context.abortWith(Response.status(Response.Status.UNAUTHORIZED)
.type(MediaType.APPLICATION_JSON)
.entity(ErrorResponse.authenticationRequired())
.build());
}
}
Now I'd like to write a test for it, mocking the ContainerRequestContext object.
#Test
public void emptyHeader() throws Exception {
when(context.getHeaderString(HttpHeaders.AUTHORIZATION)).thenReturn(null);
filter.filter(context);
Response r = Response.status(Response.Status.UNAUTHORIZED)
.type(MediaType.APPLICATION_JSON)
.entity(ErrorResponse.authenticationRequired())
.build();
verify(context).abortWith(eq(r));
}
This test fails on the eq(r) call, even if looking at the string representation of the Response objects they are the same. Any idea what's wrong?
Since I had the same question, I did it like this:
#Test
public void abort() {
new MyFilter().filter(requestContext);
ArgumentCaptor<Response> responseCaptor = ArgumentCaptor.forClass(Response.class);
verify(requestContext).abortWith(responseCaptor.capture());
Response response = responseCaptor.getValue();
assertNotNull(response);
JerseyResponseAssert.assertThat(response)
.hasStatusCode(Response.Status.FORBIDDEN);
}
I don't believe you need the eq() method. You should verify that context. abortWith(r) was called. I might be missing something though because you've not included what eq(r) is.