Mock external server during integration testing with Spring

After playing a bit with various scenarios, here is the one way how can one achieve what was asked with minimal interventions to the main code

  1. Refactor your controller to use a parameter for thirdparty server address:

    @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’ equals to ‘graph.facebook.com’ in application.properties in the src/main/resources

  1. Create a new controller in the src/test/java folder that mocks the thirdparty server.

  2. Override ‘api_host’ for testing to ‘localhost’.

Here is the code for steps 2 and 3 in one file for brevity:

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

Is there a nicer way to do this? All feedback is appreciated!

P.S. Here are some complications I encountered putting this answer into more realistic app:

  1. Eclipse mixes test and main configuration into classpath so you might screw up your main configuration by test classes and parameters: https://issuetracker.springsource.com/browse/STS-3882 Use gradle bootRun to avoid it

  2. You have to open access to your mocked links in the security config if you have spring security set up. To append to a security config instead of messing with a main configuration config:

    @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. It is not straightforward to hit https links in integration tests. I end up using TestRestTemplate with custom request factory and configured SSLConnectionSocketFactory.

Leave a Comment