Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RESTful Services test with RestTemplate

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!

like image 697
quma Avatar asked Jul 31 '15 07:07

quma


People also ask

How do I call API from RestTemplate?

Consuming PUT API by using RestTemplate - exchange() method Assume this URL http://localhost:8080/products/3 returns the below response and we are going to consume this API response by using Rest Template. Autowired the Rest Template Object. Use HttpHeaders to set the Request Headers.

Why RestTemplate is deprecated?

RestTemplate provides a synchronous way of consuming Rest services, which means it will block the thread until it receives a response. RestTemplate is deprecated since Spring 5 which means it's not really that future proof. First, we create a Spring Boot project with the spring-boot-starter-web dependency.

Can we mock RestTemplate?

We can use Mockito to mock the RestTemplate altogether. With this approach, testing our service would be as simple as any other test involving mocking. In the above JUnit test class, we first asked Mockito to create a dummy RestTemplate instance using the @Mock annotation.


2 Answers

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.

like image 71
rieckpil Avatar answered Sep 29 '22 16:09

rieckpil


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";
            }
        };
    }
}
like image 43
Raniz Avatar answered Sep 29 '22 15:09

Raniz