使用RestTemplate进行RESTful Services测试

时间:2015-07-31 07:00:50

标签: java rest junit spring-boot

在我的应用程序中,我有很多REST服务。我已经为所有服务编写了测试:

org.springframework.web.client.RestTemplate

REST服务调用,例如看起来像这样:

final String loginResponse = restTemplate.exchange("http://localhost:8080/api/v1/xy", HttpMethod.POST, httpEntity, String.class)
        .getBody();

然后我检查了响应体 - 一切正常。 缺点是必须启动应用程序才能调用REST服务。

我现在的问题是如何在JUnit- @Test方法中做到这一点? 它是一个Spring Boot应用程序(带有嵌入式tomcat)。

感谢您的帮助!

4 个答案:

答案 0 :(得分:16)

在文档中有一个很好的chapter,我建议你仔细阅读它以完全理解你能做什么。

我喜欢将@IntegrationTest与自定义配置一起使用,因为它启动整个服务器并允许您测试整个系统。如果你想用mocks替换系统的某些部分,你可以通过排除某些配置或bean并用你自己的替换它们来做到这一点。

这是一个小例子。我遗漏了MessageService界面,因为IndexController它的作用很明显,而且它的默认实施 - DefaultMessageService - 因为它&#39} ; s不相关。

它的作用是将整个应用程序减去DefaultMessageService而不是MessageService。然后,它使用RestTemplate向测试用例中正在运行的应用程序发出真实的HTTP请求。

申请类:

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();
    }
}

测试类:

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";
            }
        };
    }
}

答案 1 :(得分:9)

如果您没有寻找端到端(integretion)测试,MockRestServiceServer可能会对您有所帮助。我发现将测试用例与实际服务分离是非常有用的。

Spring doc说:

  

用于涉及直接或间接使用RestTemplate的测试。提供一种方法来设置将通过RestTemplate执行的预期请求以及模拟响应,以便 删除对实际服务器的需求

以下是official doc

还有一个提示是,requestTo无法自动导入

server.expect(manyTimes(), requestTo("/hotels/42")) ....

这是org.springframework.test.web.client.match.MockRestRequestMatchers

的静态方法

答案 2 :(得分:7)

由于您使用Spring MVC for REST,我建议使用实例化MockMVC()提供的测试工具 - 启用测试,例如:

@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())
        );
    }
}

此单元测试将测试REST接口而不进行部署。具体来说,是否返回了8个用户,而第一个用户是否包含字段','别名'和' name'。

jsonPath断言需要两个依赖项:

'com.jayway.jsonpath:json-path:0.8.1'
'com.jayway.jsonpath:json-path-assert:0.8.1'

也可能还有:

'org.springframework:spring-test:4.1.7.RELEASE'

答案 3 :(得分:1)

如果使用Spring Boot,则用RestTemplate注释测试即可轻松设置所有内容以测试@RestClientTest。这样可以确保自动配置应用程序的必需部分(RestTemplateBuilderObjectMapperMockRestServiceServer等),以测试客户端类,例如:

@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();

  }
}

相应的测试(使用JUnit 5)如下所示:

@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, "duke@java.org", "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("duke@java.org", result.getData().getEmail());
  }

}

此设置可让您使用MockRestServiceServer指定存根HTTP响应。

如果想了解更多信息,我为此提供了更多detailed tutorial