在使用Spring进行集成测试期间模拟外部服务器

时间:2015-04-09 22:23:32

标签: spring spring-security spring-boot integration-testing

我有一个Spring Web服务器,根据请求对某些第三方Web API进行外部调用(例如,retreive Facebook oauth令牌)。从此调用中获取数据后,它会计算响应:

@RestController
public class HelloController {
    @RequestMapping("/hello_to_facebook")
    public String hello_to_facebook() {
        // Ask facebook about something
        HttpGet httpget = new HttpGet(buildURI("https", "graph.facebook.com", "/oauth/access_token"));
        String response = httpClient.execute(httpget).getEntity().toString();
        // .. Do something with a response
        return response;
    }
}

我正在编写一个集成测试,用于检查在我的服务器上点击url会导致某些预期结果。但是我想在本地模拟外部服务器,这样我甚至不需要上网来测试所有这些。这样做的最佳方式是什么?

我是春天的新手,这是我到目前为止所做的。

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
@IntegrationTest({})
public class TestHelloControllerIT {        
    @Test
    public void getHelloToFacebook() throws Exception {
        String url = new URL("http://localhost:8080/hello_to_facebook").toString();
        //Somehow setup facebook server mock ...
        //FaceBookServerMock facebookMock = ...

        RestTemplate template = new TestRestTemplate();
        ResponseEntity<String> response = template.getForEntity(url, String.class);
        assertThat(response.getBody(), equalTo("..."));

        //Assert that facebook mock got called
        //facebookMock.verify();
    }
}

实际的实际设置更复杂 - 我正在进行Facebook oauth登录,并且所有逻辑都不在控制器中,而是在各种Spring Security对象中。但是我怀疑测试代码应该是相同的,因为我只是点击网址并期待回复,不是吗?

4 个答案:

答案 0 :(得分:5)

在演绎了各种场景之后,以下是一种如何通过对主要代码进行最少干预来实现所要求的方法

  1. 重构控制器以使用第三方服务器地址的参数:

    @RestController
    public class HelloController {
        @Value("${api_host}")
        private String apiHost;
    
        @RequestMapping("/hello_to_facebook")
        public String hello_to_facebook() {
            // Ask facebook about something
            HttpGet httpget = new HttpGet(buildURI("http", this.apiHost, "/oauth/access_token"));
            String response = httpClient.execute(httpget).getEntity().toString();
            // .. Do something with a response
            return response + "_PROCESSED";
        }
    }
    
  2. &#39; api_host&#39;等于&#39; graph.facebook.com&#39;在src / main / resources

    中的application.properties中
    1. 在src / test / java文件夹中创建一个模拟第三方服务器的新控制器。

    2. 覆盖&#39; api_host&#39;用于测试本地主机&#39;

    3. 以下是一个文件中步骤2和3的代码,以简洁起见:

      @RestController
      class FacebookMockController {
          @RequestMapping("/oauth/access_token")
          public String oauthToken() {
              return "TEST_TOKEN";
          }
      }
      
      @RunWith(SpringJUnit4ClassRunner.class)
      @SpringApplicationConfiguration(classes = Application.class)
      @WebAppConfiguration
      @IntegrationTest({"api_host=localhost",})
      public class TestHelloControllerIT {        
          @Test
          public void getHelloToFacebook() throws Exception {
              String url = new URL("http://localhost:8080/hello_to_facebook").toString();
              RestTemplate template = new TestRestTemplate();
              ResponseEntity<String> response = template.getForEntity(url, String.class);
              assertThat(response.getBody(), equalTo("TEST_TOKEN_PROCESSED"));
      
              // Assert that facebook mock got called:
              // for example add flag to mock, get the mock bean, check the flag
          }
      }
      

      有更好的方法吗?所有反馈都表示赞赏!

      P.S。以下是我遇到的一些复杂问题,将此答案放入更逼真的应用程序中:

      1. Eclipse将测试和主要配置混合到类路径中,因此您可能会通过测试类和参数搞砸主配置:https://issuetracker.springsource.com/browse/STS-3882使用gradle bootRun来避免它

      2. 如果设置了spring安全性,则必须在安全配置中打开对模拟链接的访问权限。要附加到安全配置而不是弄乱主配置配置:

        @Configuration
        @Order(1)
        class TestWebSecurityConfig extends WebSecurityConfig {
            @Override
            protected void configure(HttpSecurity http) throws Exception {
                http
                    .authorizeRequests()
                        .antMatchers("/oauth/access_token").permitAll();
                super.configure(http);
            }
        }
        
      3. 在集成测试中点击https链接并不简单。我最终使用TestRestTemplate和自定义请求工厂并配置了SSLConnectionSocketFactory。

答案 1 :(得分:1)

如果您在HelloController内使用RestTemplate,则可以对其进行MockRestServiceTest的测试,例如:https://www.baeldung.com/spring-mock-rest-template#using-spring-test

在这种情况下

@RunWith(SpringJUnit4ClassRunner.class)
// Importand we need a working environment
@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TestHelloControllerIT {    

    @Autowired
    private RestTemplate restTemplate;

    // Available by default in SpringBootTest env
    @Autowired
    private TestRestTemplate testRestTemplate;

    @Value("${api_host}")
    private String apiHost;

    private MockRestServiceServer mockServer;

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

    @Test
    public void getHelloToFacebook() throws Exception {

        mockServer.expect(ExpectedCount.manyTimes(),
            requestTo(buildURI("http", this.apiHost, "/oauth/access_token"))))
            .andExpect(method(HttpMethod.POST))
            .andRespond(withStatus(HttpStatus.OK)
                    .contentType(MediaType.APPLICATION_JSON)
                    .body("{\"token\": \"TEST_TOKEN\"}")
            );

        // You can use relative URI thanks to TestRestTemplate
        ResponseEntity<String> response = testRestTemplate.getForEntity("/hello_to_facebook", String.class);
        // Do the test you need
    }
}

请记住,您需要一个通用的RestTemplateConfiguration来进行自动装配,如下所示:

@Configuration
public class RestTemplateConfiguration {

    /**
     * A RestTemplate that compresses requests.
     *
     * @return RestTemplate
     */
    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}

而且您还必须在HelloController中使用它

@RestController
public class HelloController {

    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping("/hello_to_facebook")
    public String hello_to_facebook() {

        String response = restTemplate.getForEntity(buildURI("https", "graph.facebook.com", "/oauth/access_token"), String.class).getBody();
        // .. Do something with a response
        return response;
    }
}

答案 2 :(得分:0)

您可以使用另一个弹出配置文件,该文件公开与HelloController类相同的端点。然后,您可以简单地返回罐装json响应。

从您的代码中,我不确定您要完成的任务。如果你只是想看到对facebook的调用工作,那么对于实际与facebook交谈的服务进行测试是无可替代的。嘲笑Facebook的反应只是为了确保它被正确嘲笑,并不会让我觉得这是一个非常有用的测试。

如果您正在测试以查看从Facebook返回的数据是否以某种方式发生变化并且您希望确保正在进行的工作是正确的,那么您可以使用单独的方法执行该工作作为参数的facebook响应,然后进行了变异。然后,您可以根据各种json输入检查它是否正常工作。

您可以在不将Web服务引入其中的情况下进行测试。

答案 3 :(得分:0)

2018年情况有所改善。 我最终使用spring-cloud-contracts 这是视频介绍https://www.youtube.com/watch?v=JEmpIDiX7LU。演讲的第一部分将引导您完成遗留服务。这是您可以用于外部API的那个。

要点是,

  • 您可以使用Groovy DSL或其他方法为外部服务创建合同,这些方法甚至支持显式调用/代理或录制。检查有关适用对象的文档

  • 由于在这种情况下您实际上无法控制第三方,因此您将使用contract-verifier并在本地创建存根,但请记住skipTests

  • 现在stub-jar已编译并可用,您可以在测试用例中运行它,因为它将为您运行Wiremock。

这个问题和几个stackoverflow的答案帮助我找到了解决方案,所以这里是我的下一个具有这些和其他类似微服务相关测试的人的示例项目。

https://github.com/abshkd/spring-cloud-sample-games

如果一切正常,您将永远不会回头并使用spring-cloud-contracts

进行所有测试

@ marcin-grzejszczak作者,也是关于SO而且他帮了很多人解决了这个问题。所以,如果你遇到困难,只需在SO上发帖。