programing

Spring과의 통합 테스트 중 외부 서버 모의 테스트

css3 2023. 3. 26. 11:41

Spring과의 통합 테스트 중 외부 서버 모의 테스트

요청에 따라 서드파티 웹 API(예: Facebook oauth 토큰 검색)에 외부 호출을 하는 Spring 웹 서버가 있습니다.이 콜로부터 데이터를 취득하면, 다음과 같은 응답이 계산됩니다.

@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 오브젝트에 있습니다.하지만 테스트 코드는 URL을 누르기만 하면 응답을 기대할 수 있기 때문에 같은 코드라고 생각됩니다.

다양한 시나리오를 조금 사용한 후, 메인 코드에 대한 최소한의 개입으로 요구되는 것을 달성할 수 있는 방법은 다음과 같습니다.

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

'api_host'는 src/main/properties의 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
    }
}

더 좋은 방법은 없을까?모든 피드백 감사합니다!

추신: 이 답변을 보다 현실적인 앱에 넣기 위해 몇 가지 문제가 발생했습니다.

  1. Eclipse는 테스트와 메인 구성을 클래스 경로에 혼합하므로 테스트 클래스 및 파라미터별로 메인 구성을 망칠 수 있습니다.https://issuetracker.springsource.com/browse/STS-3882 gradle bootRun을 사용하여 이를 회피합니다.

  2. 스프링 보안이 설정되어 있는 경우 보안 설정에서 조롱된 링크에 대한 액세스를 열어야 합니다.메인 Configuration을 조작하지 않고 보안 Configuration에 추가하려면 다음 절차를 수행합니다.

    @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 링크를 히트하는 것은 간단하지 않습니다.커스텀 리퀘스트 팩토리 및 SSL Connection Socket Factory 설정이 끝난 TestRestTemplate를 사용하게 되었습니다.

Hello Controller 내에서 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();
    }
}

또한 Hello Controller 내부에서도 사용해야 합니다.

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

2018년 상황이 많이 좋아졌다.나는 결국 그것을 이용하게 되었다.spring-cloud-contracts여기 비디오 소개 https://www.youtube.com/watch?v=JEmpIDiX7LU가 있습니다.이 이야기의 첫 번째 부분에서는 레거시 서비스에 대해 설명합니다.외부 API에 사용할 수 있습니다.

요지는,

  • Groovy DSL 또는 명시적 콜/프록시 또는 녹음을 지원하는 다른 방법을 사용하여 외부 서비스 계약을 작성합니다.고객의 요구에 맞는 메뉴얼을 참조해 주세요.

  • 이 경우 실제로는 서드파티를 제어할 수 없기 때문에contract-verifierstub를 로컬로 만들 수 있지만 잊지 말고skipTests

  • 를 사용하여stub-jar이제 컴파일되어 사용할 수 있게 되었습니다.Wiremock이 실행되므로 테스트 케이스 내에서 실행할 수 있습니다.

이 질문과 여러 stackoverflow 답변이 솔루션을 찾는 데 도움이 되었습니다.이러한 테스트와 기타 유사한 마이크로 서비스 관련 테스트를 받은 다음 사용자를 위한 샘플프로젝트를 소개합니다.

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

모든 것이 한번 작동하면, 당신은 결코 뒤돌아보지 않고 모든 테스트를 할 수 있습니다.spring-cloud-contracts

@marcin-grzejszczak 작가도 SO를 하고 있고, 그는 이것을 알아내는 데 많은 도움을 주었다.그래서 막히면 SO에 글을 올려주세요.

HelloController 클래스와 동일한 엔드포인트를 표시하는 다른 스프링컨피규레이션파일을 사용할 수 있습니다.그런 다음 json 응답을 반환할 수 있습니다.

당신의 코드로 볼 때, 당신이 무엇을 달성하려고 하는지 잘 모르겠습니다.단순히 Facebook 콜이 기능하는 것을 보고 싶다면 Facebook과 실제로 대화하는 서비스에 대해 테스트하는 것 외에 다른 방법이 없습니다.페이스북의 응답을 조롱하는 것은 그것이 올바르게 조롱되고 있는지 확인하기 위해 나를 매우 유용한 테스트라고 생각하지 않는다.

Facebook에서 반환되는 데이터가 어떤 방식으로든 변환되는지 테스트하고 있고, 그 데이터가 올바르게 처리되고 있는지 확인하고 싶다면, Facebook 응답을 매개 변수로 하여 그 변환을 실행하는 다른 방법으로 작업을 수행할 수 있습니다.그런 다음 다양한 json 입력을 기반으로 올바르게 작동하는지 확인할 수 있습니다.

웹 서비스를 전혀 도입하지 않고 테스트할 수 있습니다.

언급URL : https://stackoverflow.com/questions/29550098/mock-external-server-during-integration-testing-with-spring