I'm using Jodd http to make requests. Is there any way to create the mock so the request doesn't happen during unit tests? I've already tried to create a mock of the send() method but without success.
#Service
class ValidateUrlService {
val TIMEOUT = 5000
fun validateUrl(url: String): RequestVO {
var response = HttpResponse()
var timeBefore = Date()
return try{
response = HttpRequest
.post(url)
.timeout(TIMEOUT)
.connectionTimeout(TIMEOUT)
.send()
val httpStatus = response.statusCode()
buildResponseDTO(httpStatusToBoolean(httpStatus), httpStatus)
} catch (ex: Exception) {
genericExceptionHandler(ex, response.statusCode(), timeBefore)
}
}
My test
internal class ValidateUrlServiceTest{
private val service = ValidateUrlService()
#Mock
var request: HttpRequest = HttpRequest.post(ArgumentMatchers.anyString())
#Test
fun test(){
Mockito.`when`(request.send()).thenReturn(HttpResponse().statusCode(555))
service.validateUrl("https://www.example.com")
}
}
Error:
You cannot use argument matchers outside of verification or stubbing.
Examples of correct usage of argument matchers:
when(mock.get(anyInt())).thenReturn(null);
doThrow(new RuntimeException()).when(mock).someVoidMethod(any());
verify(mock).someMethod(contains("foo"))
Mocking only works with the injected objects. mocking doesn't work for those objects created inside the method scope
You can abstract out the HTTP calls to a different class
class HttpService {
fun post(url: String, timeOut: Long): HttpResponse {
return HttpRequest
.post(url)
.timeout(timeOut)
.connectionTimeout(timeOut)
.send()
}
}
class ValidateUrlService {
val httpService: HttpService
val TIMEOUT = 5000
fun validateUrl(url: String): RequestVO {
var response = HttpResponse()
var timeBefore = Date()
return try{
response = httpService.post(url, TIMEOUT)
val httpStatus = response.statusCode()
buildResponseDTO(httpStatusToBoolean(httpStatus), httpStatus)
} catch (ex: Exception) {
genericExceptionHandler(ex, response.statusCode(), timeBefore)
}
}
Now you should be able to mock the HttpService post method
Your first error is here
#Mock
var request: HttpRequest = HttpRequest.post(ArgumentMatchers.anyString())
Because you are assigning to request a non mock object
If you want to use the annotations you must write you test like this:
(I'm assuming that you are using JUnit5)
#ExtendWith(MockitoExtension::class)
internal class ValidateUrlServiceTest{
private val service = ValidateUrlService()
#Mock
lateinit var request: HttpRequest
#Test
fun test(){
Mockito.`when`(request.send()).thenReturn(HttpResponse().statusCode(555))
service.validateUrl("https://www.example.com")
}
}
The second one is that you are trying to mock an object that is created inside the method you are testing
You must provide an instance of HttpRequest through dependency injection (maybe using a provider injected as a constructor argument to ValidateUrlService)
Small tip:
if you want to avoid the "ugly" syntax Mockito.`when` you can use mokito kotlin library or mockk library, both of them wrap mockito in with a more fluent kotlin way to write tests
Related
I am writing a junit test using okhttp3.mockwebserver for a retrofit2 rest api.
The trimmed down api looks like this:
public interface MyApi{
#POST("/api/get-orders")
retrofit2.Response<Set<String>> getOrders();
#POST("/api/cxl-order")
retrofit2.Response<String> cancelOrder(String ordeId);
}
The api is then injected to another class which delegates the calls thusly:
public class MyExchange{
private final MyApi api;
public MyExchange(MyApi api){
this.api = api;
}
public final Set<String> getOrders(){
Response<Set<String>> resp = api.getOrders();
//parse the response
Set<String> result = parse( resp );
return result;
}
public final boolean cancelOrder( String orderId ){
api.cancelOrder( orderId );
//Nested Call
Set<String> orders = getOrders();
return !orders.contains(orderId);
}
}
I do the following in my test:
#Test
public void cancel_order(){
MockWebServer server = new MockWebServer();
server.start();
String orderId ="OrderId_123";
MyApi mockApi = new Retrofit.Builder().baseUrl("/").build().create(MyApi.class);
MyExchange exchange = new MyExchange(mockApi);
server.enqueue( new MockResponse().setResponseCode(HttpURLConnection.HTTP_OK, orderId));
server.enqueue( new MockResponse().setResponseCode(HttpURLConnection.HTTP_OK, Set.of()));
exchange.cancelOrder(orderId);
}
Because the implementation of cancelOrder() calls api.cancelOrder() and then api.getOrders(), I added two mocked responses corresponding to each. However, looks like only the first mocked responses gets returned. For the second (getOrders), the mock server actually tries to connect over REST and then fails by timing out.
Any ideas as to how to mock responses for nested calls?
Cheers!
I ended up using the Dispatcher to check the path of the request.
If the path ends in "get-orders", I send mocked response for Orders otherwise for cancel orders.
Dispatcher dispatcher = (request) -> {
if( request.getPath().endsWith("get-orders"){
return mock response for orders
}else if( request.getPath().endsWith("cxl-orders"){
return mock response for cancel orders
}
}
mockServer.setDispatcher(dispatcher);
I'm using Spring Boot 2.5.6 and JUnit 4.13.2. My task is to test the DELETE method
My REST controller:
#RestController
public class DomainEndpoint {
private final SomeService service;
#DeleteMapping("/domain/{id}")
public void delete(#PathVariable long id) {
service.delete(id);
}
}
My test:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#RunWith(SpringRunner.class)
public class DomainEndpointTest {
#Autowired
TestRestTemplate template;
#MockBean
SomeService service;
#Test
public void delete() {
String url = "/domain/123";
ResponseEntity<?> resp = template.exchange(url, HttpMethod.DELETE, new HttpEntity<>(""), String.class);
assertEquals(HttpStatus.NO_CONTENT, resp.getStatusCode());
}
}
As you can see, the only solution for testing the 'DELETE' method, which I found, is:
ResponseEntity<?> resp = template.exchange(url, HttpMethod.DELETE, new HttpEntity<>(""), String.class);
But params for body new HttpEntity<>("") and for return type String.class seem strange to me. Why should I use them? Can I do the same more straightway without passing unnecessary parameters?
On the other hand, TestRestTemplate template has a set of short and readable methods delete(). The problem with them - they return void and I can't check the response status code in this case.
The main question is how to test DELETE methods correctly?
Instead of passing in new HttpEntity<>("") HttpEntity has a special class variable you can use called HttpEntity.EMPTY for these situations. You can also use a return type of void.
ResponseEntity<Void> resp = template.exchange(url, HttpMethod.DELETE, HttpEntity.EMPTY, Void.class);
Two things you could improve:
Don't provide a request entity – it is marked as #Nullable
Use Void.class as return type to express that you don't expect any response body
ResponseEntity<Void> resp = restTemplate.exchange(url, HttpMethod.DELETE, null, Void.class);
Others have given different options but if you want to use the the delete method - it internally handles the http error by throwing runtime error as you can see here. You can check for HttpClientErrorException or HttpServerErrorException by using assertDoesNotThrow from JUnit5
In my opinion RestTemplate.delete method is the one to use, return type is void but status other than 2XX will throw an exception.
I am trying to mock an exchange call from a rest template but for some reason i am getting a null response from the call rather than the response entity i have specified in my test. Note - before adding the injectMocks on my service interface the rest template was trying to make an actual call, when i added that in it makes a mock call but with a null result.
#ActiveProfiles("unit-test")
#RunWith(SpringJUnit4ClassRunner.class)
#Category({ UnitTests.class })
#SpringBootTest#Import({PropertiesTestConfiguration.class})
public class MyTest {
#Mock
OAuth2RestTemplate serviceRestTemplate;
#Autowired
#InjectMocks
ServiceInterface serviceInterface;
#Test
public void getServiceResponse_Success() {
ResponseEntity<String> mockResponseEntity = new ResponseEntity<String>(mockResponseBody, HttpStatus.OK);
String url = "https://unit_test_/XXX";
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
Mockito.when(serviceRestTemplate.exchange( Matchers.anyObject(), Matchers.any(HttpMethod.class), Matchers.<HttpEntity> any(), Matchers.<Class<String>> any()) ).thenReturn(mockResponseEntity);
ServiceInterface.getClaimByClaimId(XXX);
}
}
In the method I am testing this returns null
responseEntity = serviceRestTemplate.exchange(uriBuilder.toUriString(),
method, requestEntity, String.class);
If you are using hamcrest matchers i will recommed to use is and isA methods to match the value or instance, this artical explains more about core matchers
Mockito.when(serviceRestTemplate.exchange(is(instanceOf(String.class)),
is(HttpMethod.GET),
is(HttpEntity.class),
isA(String.class)))
.thenReturn(mockResponseEntity);
I have a service class that calls a REST API to get, create, update and delete subscribers. The Uri remains the same, but the HTTP method changes as you'd expect. I want to test the correct method is given. Below is an example of the updateSubscriber and its test.
public class MyService {
HttpClient httpClient;
public MyService(HttpClient httpClient) {
this.httpClient = httpClient;
}
//...
public int updateSubscriber(Subscriber subscriber) throws ... {
// PUT is the correct method for this request
HttpResponse response = httpClient.execute( new HttpPut( "https://example.org/api/subscribers" ) );
//...
}
//...
Here is my test with JUnit and Mockito:
#RunWith(MockitoJUnitRunner.class)
public class MyServiceTest
{
#Mock
private HttpClient mockHttpClient;
#Mock
private HttpResponse mockResponse;
#Mock
private StatusLine mockStatusline;
#Mock
private HttpEntity mockEntity;
// test subject
private MyService myService;
#Before
public void setup() {
// // this will just ensure http* objects are returning our mocked instances so we can manipulate them..
// when(mockHttpClient.execute(any(HttpGet.class))).thenReturn(mockResponse);
// when(mockHttpClient.execute(any(HttpPost.class))).thenReturn(mockResponse);
// when(mockHttpClient.execute(any(HttpPut.class))).thenReturn(mockResponse);
// when(mockHttpClient.execute(any(HttpDelete.class))).thenReturn(mockResponse);
// when(mockResponse.getStatusLine()).thenReturn(mockStatusline);
// when(mockStatusline.getStatusCode()).thenReturn(HttpStatus.SC_OK);
myService = new MyService(mockHttpClient);
}
#Test
public void testUpdateSubscriber() throws ...
{
when(mockHttpClient.execute(any(HttpPut.class))).thenReturn(mockResponse);
when(mockResponse.getStatusLine()).thenReturn(mockStatusline);
when(mockStatusline.getStatusCode()).thenReturn(HttpStatus.SC_OK);
String responseString = "...";
// this is consumed by a static method which we cannot mock, so we must deal with an actual entity instance
BasicHttpEntity entity = new BasicHttpEntity();
entity.setContent(new ByteArrayInputStream(responseString.getBytes()));
when(mockResponse.getEntity()).thenReturn(entity);
// create a test case Subscriber instance
Subscriber subscriber = new Subscriber();
int statusCode = myService.updateSubscriber(subscriber);
assertEquals(HttpStatus.SC_OK, statusCode);
// just confirm that an HTTP request was made
// TODO this isn't working, still passes when wrong Http* method used
verify(mockHttpClient, times(1)).execute(any(HttpPut.class));
}
//...
However, when I (wrongfully) have the another Http* method instance, it still passes:
// this is wrong, and should fail, but passed :(
HttpResponse response = httpClient.execute( new HttpGet( "https://example.org/api/subscribers" ) );
I'd really like to be able to test this as the action performed could be wrong if the method is mistaken. This test is to ensure that the PUT method was correctly used with the HTTP request for updateSubscriber. Any ideas?
Test passes because HtppPut and HttpGet both are implementation classes of HttpRequestBase, Change the mocking from HttpRequestBase class to HttpPut class
when(mockHttpClient.execute(any(HttpPut.class))).thenReturn(mockResponse);
So now if you try with GET call Test will fail with NullPointerException since GET call has no stub
Not sure if this is the proper answer to my question but I got managed to get the tests to work as intended using a custom argument matcher:
package uk.ac.strath.matchers;
import org.apache.http.client.methods.HttpUriRequest;
import org.mockito.ArgumentMatcher;
public class HttpMethodMatcher implements ArgumentMatcher<HttpUriRequest> {
private String expectedClassName;
// constructors
public HttpMethodMatcher(String expectedClassName) {
this.expectedClassName = expectedClassName;
}
#Override
public boolean matches(HttpUriRequest httpMessage) {
if (httpMessage.getClass().getName().equals(expectedClassName)) {
return true;
}
return false;
}
}
Now in my test, I can do:
verify(mockHttpClient, times(1)).execute( argThat(new HttpMethodMatcher( HttpGet.class.getName() )) );
This tutorial was helpful: https://www.baeldung.com/mockito-argument-matchers
Given a class EncoderService which has the following createNewStream method and a bunch of constants used in the method, how can I use mockito to write a unit-test for the createNewStream method:
public ResponseEntity<Object> createNewStream(Long channelId) {
String url = IP + VERSION + serverName + VHOSTS + vhostName + APP_NAME + appName + STREAM_FILES;
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON_UTF8));
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
headers.setAcceptCharset(Arrays.asList(Charset.forName(UTF_8)));
RestTemplate restTemplate = new RestTemplate();
String udp = "udp://" + "localhost" + ":" + "1935";
Map<String, String> map = new HashMap<>();
map.put("name", STREAMS + appName + channelId);
map.put("serverName", serverName);
map.put("uri", udp);
HttpEntity<Map<String, String>> request = new HttpEntity<>(map, headers);
HttpStatus statusCode = null;
try {
ResponseEntity<Object> response = restTemplate.postForEntity(url, request, Object.class);
statusCode = response.getStatusCode();
map.put(MESSAGE, "successful");
return new ResponseEntity<>(map, statusCode);
} catch (HttpStatusCodeException e) {
map.put(MESSAGE, e.getMessage());
return new ResponseEntity<>(map, HttpStatus.BAD_REQUEST);
}
}
RestTemplate is a class, not an interface, and it implements the actual HTTP transport. Both are standing in the way of writing a testable method. On top of that the fact that you are constructing an instance of a class that has side effects on the OS level rather than getting it injected does not help the case. So the way to solve it is:
write your method based around an interface rather than an implementation, RestOperations in this case
inject an instance implementing RestOperations, e.g. an instance of RestTemplate for production, via a constructor argument (preferred), method argument or via a Supplier<RestOperations> defined as a field on the class
substitute an actual instance with a test implementation or a mock in test. I guess it is easier to go for a Mockito.mock(RestOperations.class) because RestOperations just like all other Spring interfaces defines way too many method for writing a test implementation manually
So in EncoderService you can have:
private final RestOperations restClient;
public EncoderService(RestOperations restClient) {
this.restClient = restClient;
}
public ResponseEntity<Object> createNewStream(Long channelId) {
...
ResponseEntity<Object> response = restClient.postForEntity(...
...
}
And then in EncoderServiceTest:
ResponseEntity<Object> expectedReturnValue = ...
RestOperations testClient = mock(RestOperations.class);
doReturn(expectedReturnValue).when(testClient).postForEntity(any(), any(), anyClass());
EncoderService service = new EncoderService(testClient);
// use the service
For the other two cases the test setup is exactly the same, just you would pass the instance into the method call instead of constructor or overwrite the supplier on the EncoderService instance to return the testClient.
I have answered a very similar question about ProcessBuilder which also has side effects on the OS level and was constructed directly in the method under test here Error trying to mock constructor for ProcessBuilder using PowerMockito You can apply exactly the same tactics.