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

oto*_*nan 13 spring integration-testing spring-security spring-boot

我有一个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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我正在编写一个集成测试,检查在我的服务器上点击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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

小智 7

如果您在 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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

@Configuration
public class RestTemplateConfiguration {

    /**
     * A RestTemplate that compresses requests.
     *
     * @return RestTemplate
     */
    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}
Run Code Online (Sandbox Code Playgroud)

而且你也必须在 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;
    }
}
Run Code Online (Sandbox Code Playgroud)


oto*_*nan 6

在演绎了各种场景之后,这里有一种方法可以通过对主代码的最小干预来实现所要求的内容

  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";
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

'api_host'等于src/main/resources中application.properties中的'graph.facebook.com'

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

  2. 覆盖'api_host'以测试'localhost'.

为简洁起见,以下是一个文件中步骤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
    }
}
Run Code Online (Sandbox Code Playgroud)

有没有更好的方法来做到这一点?所有反馈表示赞赏!

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

  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);
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 在集成测试中点击https链接并不简单.我最终使用TestRestTemplate与自定义请求工厂和配置SSLConnectionSocketFactory.