spring

Using MockRestServiceServer to Test a REST Client

This article is an example in using MockRestServiceServer to test a REST client. MockRestServiceServer is a part of the Spring library for testing. It is the main entry point for client-side REST testing. It provides mock responses from expected requests through the RestTemplate. It eliminates the use of an actual server and thus speeds up the testing process.

With its operations, we can verify that all requests were performed. We can reset the internal state thus removing all expectations and requests. We can set up an expectation for single or multiple HTTP requests.
 
 
 

1. Assumptions

This article goes straight into discussing about using MockRestServiceServer to test a REST client. It is assumed that the reader is familiar with some of the following concepts:

  1. Spring
  2. Mars Eclipse or any IDE
  3. JUnit
  4. Apache Maven

2. Code Examples

ExampleRestService is found in the com.javacodegeeks.example.service main package.

ExampleRestService.java

package com.javacodegeeks.example.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;

@Service
public class ExampleRestService {
	
	@Autowired
	private RestTemplate restTemplate;
	
	public String getRootResource() {
		String result = restTemplate.getForObject("http://localhost:8080", String.class);
		System.out.println("getRootResource: " + result);
		
		return result;
	}
	
	public String addComment(String comment) {
		String result = null;
		try {
			result = restTemplate.postForObject("http://localhost/add-comment", comment, String.class);
			System.out.println("addComment: " + result);
		} catch (HttpClientErrorException e) {
			result = e.getMessage();
		}
		
		return result;
	}

}

The ExampleRestService class is a service layer class. A common layer that uses the service layer is the presentation layer. There are two services provided by ExampleRestService, getRootResource and addComment. The getRootResource operation communicates to a URL using RestTemplate and returns the result to the presentation layer. The second operation named addComment accepts a comment from the presentation layer then posts it to a URL via RestTemplate. It then returns a result to the presentation layer specifying whether it was successful or not. Just imagine that the addComment operation is adding a comment on a Facebook post.

To test ExampleRestService, we have the ExampleRestServiceTestViaRestGateway and ExampleRestServiceTest in the com.javacodegeeks.example.service test package.

ExampleRestServiceTestViaRestGateway.java

package com.javacodegeeks.example.service;

import static org.junit.Assert.assertEquals;
import static org.springframework.test.web.client.ExpectedCount.once;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.support.RestGatewaySupport;

import com.javacodegeeks.example.service.ExampleRestService;

@RunWith(SpringRunner.class)
@SpringBootTest
public class ExampleRestServiceTestViaRestGateway {
	
	@Autowired
	RestTemplate restTemplate;

	@Autowired
	ExampleRestService service;

	private MockRestServiceServer mockServer;

	@Before
	public void setUp() {
		RestGatewaySupport gateway = new RestGatewaySupport();
		gateway.setRestTemplate(restTemplate);
		mockServer = MockRestServiceServer.createServer(gateway);
	}
	
	@Test
	public void testGetRootResourceOnce() {
		mockServer.expect(once(), requestTo("http://localhost:8080"))
			.andRespond(withSuccess("{message : 'under construction'}", MediaType.APPLICATION_JSON));

		String result = service.getRootResource();
		System.out.println("testGetRootResourceOnce: " + result);

		mockServer.verify();
		assertEquals("{message : 'under construction'}", result);
	}

}

The ExampleRestServiceTestViaRestGateway class will test the ExampleRestService class by mocking a REST server. As shown in ExampleRestService, the REST server is localhost. Instead of running an actual REST server, a mock server is used to mimic the actual REST server. The fake server is created using Spring’s MockRestServiceServer. This makes testing cheap and fast. Regression testing can be done several times a day.

The @RunWith annotation means that instead of using the built-in JUnit test runner, SpringRunner.class will become the test runner. SpringRunner is the new name of SpringJUnit4ClassRunner. The @SpringBootTest means to add Spring Boot support to the test (e.g. no XML configuration). The @Autowired annotation tells Spring where an injection should occur. For example, Spring will automatically create a bean of type RestTemplate and inject it to the restTemplate field. The @Before annotation tells the test runner that it should be called before every test. For every test a new RestGatewaySupport is created and a new MockRestServiceServer is also created. The @Test annotation on a method marks it as a single test. This is the actual test. The testGetRootResourceOnce mocks the REST server ExampleRestService will be communicating to via RestTemplate. This test expects that ExampleRestService will call the REST server (HTTP request) only once. It expects the URL of the REST server to be http://localhost:8080 and will respond successfully with a JSON message. If these expectations are not met, the verify method will fail this test.

The line “String result = service.getRootResource();” tests the getRootResource method of ExampleRestService. The assertEquals line below checks if the expected result is equivalent to the actual result returned by the getRootResource method. The test passes if it is the same.

ExampleRestServiceTest.java

package com.javacodegeeks.example.service;

import static org.junit.Assert.assertEquals;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestTemplate;

import com.javacodegeeks.example.service.ExampleRestService;

import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withBadRequest;
import static org.springframework.test.web.client.ExpectedCount.once;
import static org.springframework.test.web.client.ExpectedCount.times;

@RunWith(SpringRunner.class) //SpringRunner is an alias for the SpringJUnit4ClassRunner
@SpringBootTest
public class ExampleRestServiceTest {

	@Autowired
	RestTemplate restTemplate;

	@Autowired
	ExampleRestService service;

	private MockRestServiceServer mockServer;

	@Before
	public void setUp() {
		mockServer = MockRestServiceServer.createServer(restTemplate);
	}

	@Test
	public void testGetRootResource() {
		mockServer.expect(requestTo("http://localhost:8080")).andRespond(withSuccess("hello", MediaType.TEXT_PLAIN));

		String result = service.getRootResource();
		System.out.println("testGetRootResource: " + result);

		mockServer.verify();
		assertEquals("hello", result);
	}
	
	@Test
	public void testGetRootResourceOnce() {
		mockServer.expect(once(), requestTo("http://localhost:8080"))
			.andRespond(withSuccess("{message : 'under construction'}", MediaType.APPLICATION_JSON));

		String result = service.getRootResource();
		System.out.println("testGetRootResourceOnce: " + result);

		mockServer.verify();
		assertEquals("{message : 'under construction'}", result);
	}
	
	@Test
	public void testGetRootResourceTimes() {
		mockServer.expect(times(2), requestTo("http://localhost:8080"))
			.andRespond(withSuccess("{message : 'under construction'}", MediaType.APPLICATION_JSON));

		String result = service.getRootResource();
		System.out.println("testGetRootResourceTimes: " + result);

		mockServer.verify(); // should fail because this test expects RestTemplate.getForObject to be called twice 
		assertEquals("{message : 'under construction'}", result);
	}
	
	@Test
	public void testAddComment() {
		mockServer.expect(requestTo("http://localhost/add-comment")).andExpect(method(HttpMethod.POST))
			.andRespond(withSuccess("{post : 'success'}", MediaType.APPLICATION_JSON));

		String result = service.addComment("cute puppy");
		System.out.println("testAddComment: " + result);

		mockServer.verify();
		assertEquals("{post : 'success'}", result);
	}
	
	@Test
	public void testAddCommentClientError() {
		mockServer.expect(requestTo("http://localhost/add-comment")).andExpect(method(HttpMethod.POST))
			.andRespond(withBadRequest());

		String result = service.addComment("cute puppy");
		System.out.println("testAddCommentClientError: " + result);

		mockServer.verify();
		assertEquals("400 Bad Request", result);
	}
	
	@Test
	public void testReset() {
		mockServer.expect(requestTo("http://localhost/add-comment")).andExpect(method(HttpMethod.POST))
			.andRespond(withSuccess("{post : 'success'}", MediaType.APPLICATION_JSON));

		String result = service.addComment("cute puppy");
		System.out.println("testReset 1st: " + result);

		mockServer.verify();
		assertEquals("{post : 'success'}", result);
		
		mockServer.reset();
		
		mockServer.expect(requestTo("http://localhost:8080")).andRespond(withSuccess("hello", MediaType.TEXT_PLAIN));

		result = service.getRootResource();
		System.out.println("testReset 2nd: " + result);

		mockServer.verify();
		assertEquals("hello", result);
	}
}

The ExampleRestServiceTest also tests the ExampleRestService. The difference with the earlier example is that this test uses RestTemplate to create the server instead of RestGateWaySupport. The annotations used in this class are the same as the previous example. The class contains six tests and one of the test is designed to fail.

In the testGetRootResource method, if the expected count (e.g., once()) is not specified then by default it expects a single HTTP request.

The testGetRootResourceTimes will fail because it expects two HTTP requests to the REST server but ExampleRestService only invokes RestTemplate’s getForObject method once.

The testAddComment method simulates an HTTP POST request. The mock server has two expectations, a specified URL and certain HTTP request method.

In testAddCommentClientError, a client error is simulated. The mock server returns an HTTP status code signifying an HTTP client error (e.g., malformed request).

In the testReset method, the service is called twice. The mock server didn’t fail the test because a reset is made (i.e., mockServer.reset() is invoked between each single call). The MockRestServiceServer reset operation removes all expectations and recorded requests.

3. Async Code Examples

AsyncExampleRestService is found in the com.javacodegeeks.example.service main package.

AsyncExampleRestService.java

package com.javacodegeeks.example.service;

import java.util.concurrent.ExecutionException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.web.client.AsyncRestTemplate;
import org.springframework.web.client.HttpServerErrorException;

@Service
public class AsyncExampleRestService {

	@Autowired
	private AsyncRestTemplate asyncRestTemplate;
	
	public String deleteAllSuspendedUsers() {
		ListenableFuture future = asyncRestTemplate.delete("http://localhost/delete-all-suspended-users");
		// doing some long process here...
		Object result = null;
		String returnValue = "";
		try {
			result = future.get(); //The Future will return a null result upon completion.
			if (result == null) {
				returnValue = "{result:'success'}";
			} else {
				returnValue = "{result:'fail'}";
			}
		} catch (InterruptedException | ExecutionException e) {
			if (e.getCause() instanceof HttpServerErrorException) {
				returnValue = "{result: 'server error'}";
			}
		}
		System.out.println("deleteAllSuspendedUsers: " + result);
		
		return returnValue;
	}
}

AsyncExampleRestService uses Spring’s AsyncRestTemplate class to access a REST server. AsyncRestTemplate is similar to RestTemplate and is used for asynchronous client-side HTTP access. The operation in this service is simulating a delete of all suspended users which might take a significant amount of time. That is why we’re doing it asynchronously. It doesn’t need to wait or block to perform the next lines of code. The future.get() statement blocks and returns null if the AsyncRestTemplate.delete call is finished or throws an Exception when something went wrong.

To test AsyncExampleRestService, we have the AsyncExampleRestServiceTest in the com.javacodegeeks.example.service test package.

AsyncExampleRestServiceTest.java

package com.javacodegeeks.example.service;

import static org.junit.Assert.assertEquals;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withServerError;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpMethod;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.AsyncRestTemplate;

import com.javacodegeeks.example.service.AsyncExampleRestService;

@RunWith(SpringRunner.class)
@SpringBootTest
public class AsyncExampleRestServiceTest {

	@Autowired
	AsyncRestTemplate asyncRestTemplate;

	@Autowired
	AsyncExampleRestService service;

	private MockRestServiceServer mockServer;

	@Before
	public void setUp() {
		mockServer = MockRestServiceServer.createServer(asyncRestTemplate);
	}

	@Test
	public void testDeleteAllSuspendedUsers() {
		mockServer.expect(requestTo("http://localhost/delete-all-suspended-users")).andExpect(method(HttpMethod.DELETE))
			.andRespond(withServerError());

		String result = service.deleteAllSuspendedUsers();
		System.out.println("testDeleteAllSuspendedUsers: " + result);

		mockServer.verify();
		assertEquals("{result: 'server error'}", result);
	}

}


The testDeleteAllSuspendedUsers method is similar to the other test methods. The difference is the mock server expects an HTTP DELETE request and a server error response. Instead of an HTTP status code 5xx (e.g., 500 – Internal Server Error) to indicate a server error, the service layer returns a JSON string. The HttpServerErrorException was handled by AsyncExampleRestService and in turn returned a JSON string to signify the error. The withServerError call triggers the fake server to generate a server error.

4. Run the Tests

The test output should look like the one below:
 
ExampleRestServiceTest Output

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.1.RELEASE)

2017-09-07 14:10:49.438  INFO 7916 --- [           main] c.j.e.service.ExampleRestServiceTest     : Starting ExampleRestServiceTest on asus_k43s with PID 7916 (started by jpllosa in D:\javacodegeeks_com\mockrestserviceserver\mockrestserviceserver-example)
2017-09-07 14:10:49.441  INFO 7916 --- [           main] c.j.e.service.ExampleRestServiceTest     : No active profile set, falling back to default profiles: default
2017-09-07 14:10:49.621  INFO 7916 --- [           main] o.s.w.c.s.GenericWebApplicationContext   : Refreshing org.springframework.web.context.support.GenericWebApplicationContext@22fcf7ab: startup date [Thu Sep 07 14:10:49 BST 2017]; root of context hierarchy
2017-09-07 14:10:52.386  INFO 7916 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration' of type [class org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2017-09-07 14:10:52.567  INFO 7916 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'validator' of type [class org.springframework.validation.beanvalidation.LocalValidatorFactoryBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2017-09-07 14:10:54.738  INFO 7916 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.web.context.support.GenericWebApplicationContext@22fcf7ab: startup date [Thu Sep 07 14:10:49 BST 2017]; root of context hierarchy
2017-09-07 14:10:55.028  INFO 7916 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/]}" onto public java.lang.String com.javacodegeeks.example.MockRestServiceServerExample.getRootResource()
2017-09-07 14:10:55.048  INFO 7916 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2017-09-07 14:10:55.052  INFO 7916 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2017-09-07 14:10:55.237  INFO 7916 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-09-07 14:10:55.238  INFO 7916 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-09-07 14:10:55.392  INFO 7916 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-09-07 14:10:55.839  INFO 7916 --- [           main] c.j.e.service.ExampleRestServiceTest     : Started ExampleRestServiceTest in 7.163 seconds (JVM running for 9.03)
getRootResource: {message : 'under construction'}
testGetRootResourceOnce: {message : 'under construction'}
addComment: {post : 'success'}
testReset 1st: {post : 'success'}
getRootResource: hello
testReset 2nd: hello
testAddCommentClientError: 400 Bad Request
getRootResource: hello
testGetRootResource: hello
getRootResource: {message : 'under construction'}
testGetRootResourceTimes: {message : 'under construction'}
addComment: {post : 'success'}
testAddComment: {post : 'success'}
2017-09-07 14:10:56.235  INFO 7916 --- [       Thread-3] o.s.w.c.s.GenericWebApplicationContext   : Closing org.springframework.web.context.support.GenericWebApplicationContext@22fcf7ab: startup date [Thu Sep 07 14:10:49 BST 2017]; root of context hierarchy

5. Using MockRestServiceServer to Test a REST Client Summary

MockRestServiceServer is used for testing the client side. We should create an instance of it by using an instance of RestTemplate that is being used by our production code. We don’t create a new RestTemplate in our test.
After each test, the verify method must be called after the RestTemplate is called to run the MockRestServiceServer assertions.

6. Download the Source Code

 
This is an example of using MockRestServiceServer to test a REST client.

Download
You can download the source code of this example here: mockrestserviceserver-example.zip.

Joel Patrick Llosa

I graduated from Silliman University in Dumaguete City with a degree in Bachelor of Science in Business Computer Application. I have contributed to many Java related projects at Neural Technologies Ltd., University of Southampton (iSolutions), Predictive Technologies, LLC., Confluence Service, North Concepts, Inc., NEC Telecom Software Philippines, Inc., and NEC Technologies Philippines, Inc. You can also find me in Upwork freelancing as a Java Developer.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

5 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Ida
Ida
3 years ago

In my production code I need to execute POST command to a controller which response StreamingResponseBody. In the testing I would like to mock the response from this controller. How do I respond with StreamingResponseBody in MockRestServiceServer? An example to the code can be found at https://stackoverflow.com/questions/61045521/how-to-mock-streamingresponsebody-in-testing

vishal
vishal
3 years ago

It gives me error 404 error. Any fixes/suggestions ?

Raju
Raju
2 years ago

Thank you bro,

Back to top button