在使用 Spring 进行集成测试期间模拟外部服务器
Mock external server during integration testing with Spring
我有一个 Spring 网络服务器,它根据请求对某些第三方网络进行外部调用 API(例如检索 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 是否会产生一些预期的结果。但是我想在本地模拟外部服务器,这样我什至不需要互联网访问来测试这一切。最好的方法是什么?
我是spring的新手,这是我目前的情况。
@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 安全对象中。但是我怀疑测试代码应该是相同的,因为我只是点击 urls 并期待响应,不是吗?
您可以有另一个 spring 配置文件,它公开与 HelloController class 相同的端点。然后,您可以简单地 return 罐头 json 响应。
根据您的代码,我不确定您想要完成什么。如果您只是想查看对 facebook 的调用是否有效,那么除了针对实际与 facebook 对话的服务进行测试之外,别无他法。模拟 facebook 响应只是为了确保它被正确模拟,并没有让我觉得这是一个非常有用的测试。
如果您正在测试以查看从 facebook 返回的数据以某种方式发生了变异,并且您想确保对其所做的工作是正确的,那么您可以使用单独的方法来完成这项工作将 facebook 响应作为参数,然后进行突变。然后,您可以根据各种 json 输入来检查它是否正常工作。
您完全可以在不引入 Web 服务的情况下进行测试。
在尝试了各种场景之后,这是一种如何在对主代码进行最少干预的情况下实现要求的方法
重构您的控制器以使用第三方服务器地址的参数:
@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";
}
}
'api_host' 等于 'graph.facebook.com' 在 application.properties 在 src/main/resources
在模拟第三方服务器的 src/test/java 文件夹中创建一个新控制器。
覆盖 '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
}
}
有更好的方法吗?感谢所有反馈!
P.S。以下是我将此答案放入更现实的应用程序时遇到的一些复杂问题:
Eclipse 将测试和主要配置混合到类路径中,因此您可能会通过测试 类 和参数搞砸您的主要配置:https://issuetracker.springsource.com/browse/STS-3882 使用 gradle bootRun 来避免它
如果您设置了 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);
}
}
在集成测试中点击 https 链接并不简单。我最终将 TestRestTemplate 与自定义请求工厂一起使用并配置了 SSLConnectionSocketFactory.
2018年情况有了很大的改善。
我最终使用了 spring-cloud-contracts
这里有一个视频介绍 https://www.youtube.com/watch?v=JEmpIDiX7LU 。演讲的第一部分将带您了解遗留服务。这是您可以用于外部 API 的那个。
要点是,
您使用 Groovy DSL 或其他甚至支持显式 calls/proxy 或记录的方法为外部服务创建合同。查看适合您的文档
由于在这种情况下您实际上无法控制第 3 方,因此您将使用 contract-verifier
并在本地创建存根,但请记住 skipTests
随着 stub-jar
现已编译并可用,您可以 运行 在您的测试用例中使用它,因为它将 运行 为您提供 Wiremock。
这个问题和几个 Whosebug 的答案帮助我找到了解决方案,所以这是我的示例项目,供下一个进行这些和其他类似微服务相关测试的人使用。
https://github.com/abshkd/spring-cloud-sample-games
一旦一切正常,您将永远不会回头用 spring-cloud-contracts
进行所有测试
@marcin-grzejszczak 作者也在 SO 上,他帮助解决了很多问题。因此,如果您遇到困难,只需 post 就可以了。
如果您在 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;
}
}
我有一个 Spring 网络服务器,它根据请求对某些第三方网络进行外部调用 API(例如检索 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 是否会产生一些预期的结果。但是我想在本地模拟外部服务器,这样我什至不需要互联网访问来测试这一切。最好的方法是什么?
我是spring的新手,这是我目前的情况。
@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 安全对象中。但是我怀疑测试代码应该是相同的,因为我只是点击 urls 并期待响应,不是吗?
您可以有另一个 spring 配置文件,它公开与 HelloController class 相同的端点。然后,您可以简单地 return 罐头 json 响应。
根据您的代码,我不确定您想要完成什么。如果您只是想查看对 facebook 的调用是否有效,那么除了针对实际与 facebook 对话的服务进行测试之外,别无他法。模拟 facebook 响应只是为了确保它被正确模拟,并没有让我觉得这是一个非常有用的测试。
如果您正在测试以查看从 facebook 返回的数据以某种方式发生了变异,并且您想确保对其所做的工作是正确的,那么您可以使用单独的方法来完成这项工作将 facebook 响应作为参数,然后进行突变。然后,您可以根据各种 json 输入来检查它是否正常工作。
您完全可以在不引入 Web 服务的情况下进行测试。
在尝试了各种场景之后,这是一种如何在对主代码进行最少干预的情况下实现要求的方法
重构您的控制器以使用第三方服务器地址的参数:
@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"; } }
'api_host' 等于 'graph.facebook.com' 在 application.properties 在 src/main/resources
在模拟第三方服务器的 src/test/java 文件夹中创建一个新控制器。
覆盖 '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
}
}
有更好的方法吗?感谢所有反馈!
P.S。以下是我将此答案放入更现实的应用程序时遇到的一些复杂问题:
Eclipse 将测试和主要配置混合到类路径中,因此您可能会通过测试 类 和参数搞砸您的主要配置:https://issuetracker.springsource.com/browse/STS-3882 使用 gradle bootRun 来避免它
如果您设置了 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); } }
在集成测试中点击 https 链接并不简单。我最终将 TestRestTemplate 与自定义请求工厂一起使用并配置了 SSLConnectionSocketFactory.
2018年情况有了很大的改善。
我最终使用了 spring-cloud-contracts
这里有一个视频介绍 https://www.youtube.com/watch?v=JEmpIDiX7LU 。演讲的第一部分将带您了解遗留服务。这是您可以用于外部 API 的那个。
要点是,
您使用 Groovy DSL 或其他甚至支持显式 calls/proxy 或记录的方法为外部服务创建合同。查看适合您的文档
由于在这种情况下您实际上无法控制第 3 方,因此您将使用
contract-verifier
并在本地创建存根,但请记住skipTests
随着
stub-jar
现已编译并可用,您可以 运行 在您的测试用例中使用它,因为它将 运行 为您提供 Wiremock。
这个问题和几个 Whosebug 的答案帮助我找到了解决方案,所以这是我的示例项目,供下一个进行这些和其他类似微服务相关测试的人使用。
https://github.com/abshkd/spring-cloud-sample-games
一旦一切正常,您将永远不会回头用 spring-cloud-contracts
@marcin-grzejszczak 作者也在 SO 上,他帮助解决了很多问题。因此,如果您遇到困难,只需 post 就可以了。
如果您在 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;
}
}