Mock внешний сервер во время тестирования интеграции с помощью Spring

У меня есть веб-сервер Spring, который по запросу делает внешний вызов некоторого стороннего веб-API (например, retreive Facebook oauth token). После получения данных от этого вызова он вычисляет ответ:

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

Фактическая реальная настройка сложнее - я делаю логин для входа в систему o o ot, и вся эта логика находится не в контроллере, а в различных объектах безопасности Spring. Однако я подозреваю, что код тестирования должен быть таким же, поскольку я просто нажимаю 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' равен 'graph.facebook.com' в application.properties в src/main/resources

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

Есть ли лучший способ сделать это? Все отзывы приветствуются!

P.S. Вот некоторые сложности, с которыми я столкнулся, поставив этот ответ в более реалистичное приложение:

  • Eclipse смешивает тест и основную конфигурацию в classpath, поэтому вы можете испортить свою основную конфигурацию с помощью тестовых классов и параметров: 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 с пользовательским запросом factory и настроил SSLConnectionSocketFactory.

Ответ 2

У вас может быть другой файл конфигурации spring, который предоставляет ту же конечную точку, что и класс HelloController. Затем вы можете просто вернуть законченный ответ json.

Из вашего кода я не уверен, что вы пытаетесь выполнить. Если вы просто хотите увидеть, что вызов в facebook работает, то нет никакой замены для тестирования на службу, которая фактически разговаривает с facebook. Издеваясь над ответом на facebook, чтобы убедиться, что он издевается правильно, не кажется мне ужасно полезным тестом.

Если вы тестируете, чтобы увидеть, что данные, возвращаемые с facebook, каким-то образом мутируются, и вы хотите убедиться, что работа над ним выполнена правильно, тогда вы можете сделать эту работу в отдельном методе, который потребовался ответ facebook как параметр, а затем выполнил мутацию. Затем вы можете проверить на основе различных json-входов, что он работает правильно.

Вы можете протестировать, не введя в него веб-сервис.