Reputation: 5733
In my application I have a lot of REST- Services. I have written tests for all services with:
org.springframework.web.client.RestTemplate
A REST- Service invokation e.g. looks like this:
final String loginResponse = restTemplate.exchange("http://localhost:8080/api/v1/xy", HttpMethod.POST, httpEntity, String.class)
.getBody();
and I check the response body afterwards - all works fine. The disadvantage is, that the application for sure must be started in order to invoke the REST- Services.
My question now would be how I can do this in my JUnit- @Test methods? It is a Spring Boot application (with embedded tomcat).
Thanks for help!
Upvotes: 12
Views: 49211
Reputation: 12021
If you use Spring Boot, you can easily setup everything to test your RestTemplate
if you annotate your test with @RestClientTest
. This ensures to auto-configure the required parts (RestTemplateBuilder
, ObjectMapper
, MockRestServiceServer
, etc.) of your application to test your client classes like e.g.:
@Component
public class UserClient {
private final RestTemplate restTemplate;
public UserClient(RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.rootUri("https://reqres.in").build();
}
public User getSingleUser(Long id) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Void> requestEntity = new HttpEntity<>(headers);
return this.restTemplate
.exchange("/api/users/{id}", HttpMethod.GET, requestEntity, User.class, id)
.getBody();
}
}
The corresponding test (using JUnit 5) looks like the following:
@RestClientTest(UserClient.class)
class UserClientTest {
@Autowired
private UserClient userClient;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private MockRestServiceServer mockRestServiceServer;
@Test
public void userClientSuccessfullyReturnsUserDuke() throws Exception {
String json = this.objectMapper
.writeValueAsString(new User(new UserData(42L, "[email protected]", "duke", "duke", "duke")));
this.mockRestServiceServer
.expect(requestTo("/api/users/42"))
.andRespond(withSuccess(json, MediaType.APPLICATION_JSON));
User result = userClient.getSingleUser(42L);
assertEquals(42L, result.getData().getId());
assertEquals("duke", result.getData().getFirstName());
assertEquals("duke", result.getData().getLastName());
assertEquals("duke", result.getData().getAvatar());
assertEquals("[email protected]", result.getData().getEmail());
}
}
This setup allows you to specify stub HTTP responses using MockRestServiceServer
.
I've provided a more detailed tutorial for this, if wan to learn more about it.
Upvotes: 1
Reputation: 726
If you were not looking for a end to end (integretion) test, the MockRestServiceServer
might help you. I found it's very useful to de-couple my test cases from a real service.
Spring doc said:
Used for tests that involve direct or indirect use of the RestTemplate. Provides a way to set up expected requests that will be performed through the RestTemplate as well as mock responses to send back thus removing the need for an actual server.
Here is the official doc
One more tip is that, requestTo
can not be imported automatically
server.expect(manyTimes(), requestTo("/hotels/42")) ....
It's a static method of org.springframework.test.web.client.match.MockRestRequestMatchers
Upvotes: 9
Reputation: 1323
Since you are using Spring MVC for REST, I would recommend using the testing facilities supplied by instantiating MockMVC() - enabling tests such as:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {
... // any required Spring config
)
@WebAppConfiguration
public class RestControllerTest {
@Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
@Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
}
@Test
public void getUserList() throws Exception {
mockMvc.perform(get("/user"))
.andExpect(status().isOk())
.andExpect(content().contentType("application/json;charset=UTF-8"))
.andExpect(content().encoding("UTF-8"))
.andExpect(jsonPath("$", hasSize(8)))
.andExpect(jsonPath("$[0].id").exists())
.andExpect(jsonPath("$[0].alias").exists())
.andExpect(jsonPath("$[0].name").exists())
);
}
}
This unit test will test a REST interface without deploying. Specifically, whether exactly 8 users are returned and the first one has the fields 'id', 'alias' and 'name'.
The jsonPath assertions require two dependencies:
'com.jayway.jsonpath:json-path:0.8.1'
'com.jayway.jsonpath:json-path-assert:0.8.1'
And probably also:
'org.springframework:spring-test:4.1.7.RELEASE'
Upvotes: 7
Reputation: 11113
There's a good chapter on this in the documentation, I suggest you read through it to fully understand what you can do.
I like to use @IntegrationTest
with a custom configuration since that starts up the entire server and lets you test the complete system. If you want to replace certain parts of the system with mocks you can do that by excluding certain configurations or beans and replacing them with your own.
Here's a small example. I've left out the MessageService
interface because it's obvious from IndexController
what it does, and it's default implementation - DefaultMessageService
- because it's not relevant.
What it does is that it spins up the entire application minus the DefaultMessageService
but with it's own MessageService
instead. It then uses RestTemplate
to issue real HTTP requests to the running application in the test case.
Application classes:
IntegrationTestDemo.java:
@SpringBootApplication
public class IntegrationTestDemo {
public static void main(String[] args) {
SpringApplication.run(IntegrationTestDemo.class, args);
}
}
IndexController.java:
@RestController
public class IndexController {
@Autowired
MessageService messageService;
@RequestMapping("/")
String getMessage() {
return messageService.getMessage();
}
}
Test classes:
IntegrationTestDemoTest.java:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TestConfig.class)
@WebIntegrationTest // This will start the server on a random port
public class IntegrationTestDemoTest {
// This will hold the port number the server was started on
@Value("${local.server.port}")
int port;
final RestTemplate template = new RestTemplate();
@Test
public void testGetMessage() {
String message = template.getForObject("http://localhost:" + port + "/", String.class);
Assert.assertEquals("This is a test message", message);
}
}
TestConfig.java:
@SpringBootApplication
@ComponentScan(
excludeFilters = {
// Exclude the default message service
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = DefaultMessageService.class),
// Exclude the default boot application or it's
// @ComponentScan will pull in the default message service
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = IntegrationTestDemo.class)
}
)
public class TestConfig {
@Bean
// Define our own test message service
MessageService mockMessageService() {
return new MessageService() {
@Override
public String getMessage() {
return "This is a test message";
}
};
}
}
Upvotes: 17