5

I am trying to come up with some integartion tests for a Spring boot service. Since the service is using AWS SQS and DynamoDB, I tend to take advantage of the Localstack module of testcontainers for the integartion tests. But although I think I have included all necessary code, the LocalStackContainer does not seem to run and the following error is thrown:

com.amazonaws.SdkClientException: Unable to execute HTTP request: Connect to localhost:4576
  [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused (Connection refused)

BTW, if I use standing alone localstack (ie, run manually in a terminal '$localstack start'), the integration tests would pass.

Could anyone please help me figure out what I am missing?

In build.gradle I have

testCompile("org.testcontainers:testcontainers:1.10.6")
testCompile("org.testcontainers:localstack:1.10.6")

In a super class I setup some shared testing context like this, including the LocalStackContainer as a @ClassRule

@RunWith(SpringJUnit4ClassRunner.class)
@ActiveProfiles("local")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class)
@Slf4j
public class BaseIntegrationTest extends CamelTestSupport {

    @Value("${isLocal:false}")
    protected boolean isLocal;

    @Value("${isLocalStack:true}")
    protected boolean isLocalStack;

    @Value("${instanceUrl}")
    protected String instanceUrl;

    @LocalServerPort
    private int serverPort;

    @EndpointInject(uri = "mock:endPoint")
    protected MockEndpoint mockEndpoint;

    protected ApplicationContext appContext;
    protected AmazonDynamoDB amazonDynamoDB;
    protected AmazonSQS amazonSQS;
    protected CamelContext camelContext;
    protected Exchange exchange;
    protected ProducerTemplate producerTemplate;

    @ClassRule
    public static LocalStackContainer localstack = new LocalStackContainer()
            .withEnv("HOSTNAME_EXTERNAL", DockerClientFactory.instance().dockerHostIpAddress())
            .withExposedPorts(4569, 4576) // mock DynamoDB (http port 4569), mock SQS (http port 4576)
            .withStartupAttempts(3)
            .withStartupTimeout(Duration.ofMinutes(3))
            .withServices(DYNAMODB, SQS);

    @BeforeClass
    public static void init() {
        localstack.start();
    }

    @AfterClass
    public static void teardown() {
        localstack.stop();
    }

    @Before
    public void setup() throws Exception {
        RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
        RestAssured.port = serverPort;

        if (instanceUrl != null) {
            RestAssured.baseURI = instanceUrl;
        } else {
            RestAssured.requestSpecification = new RequestSpecBuilder()
                    .setBaseUri(System.getProperty("APP_HOST", "http://localhost:" + serverPort))
                    .build();
        }

        String dynamoDBEndpoint = "http://" + localstack.getContainerIpAddress() 
                                            + ":" + localstack.getMappedPort(4569);
        String sqsEndpoint = "http://" + localstack.getContainerIpAddress() 
                                       + ":" + localstack.getMappedPort(4576);

        log.info("The integration test is using localstack dynamoDBEndpoint={} and sqsEndpoint={}",
                dynamoDBEndpoint, sqsEndpoint);

        appContext = new SpringApplicationBuilder(Application.class)
                .profiles("local")
                .properties("localstackDynamoDBEndpoint=" + dynamoDBEndpoint,
                        "localstackSQSEndpoint=" + sqsEndpoint)
                .run();

        amazonDynamoDB = appContext.getBean(AmazonDynamoDB.class);
        amazonSQS = appContext.getBean(AmazonSQS.class);
        camelContext = appContext.getBean(CamelContext.class);
        producerTemplate = camelContext.createProducerTemplate();
    }

    @After
    public void cleanup() {
        ((ConfigurableApplicationContext) appContext).close();
    }

}

In the test class extending the super class, it is just some restassured code like this

@Test
public void test400Response() {

    given()
            .body("")
            .contentType("application/json")
            .post("/root/my_service/v1")
            .then()
            .statusCode(HttpStatus.SC_BAD_REQUEST)
            .body("message", Matchers.equalTo("Missing expected content"))
            .log()
            .all();
}

And if you would like to have a look, here is the log for the succeeded tests

https://justpaste.it/success-with-running-localstack

and the log for the failed tests

https://justpaste.it/failed-with-localstackContainer

dunfa
  • 509
  • 1
  • 10
  • 19
  • Any success on that? I get the same error. `docker ps` shows the started testcontainers and also `localstack/localstack:latest` while the test is running, but the same `com.amazonaws.SdkClientException: Unable to execute HTTP request: The target server failed to respond` is thrown. – Dmitriy Popov Jan 29 '21 at 10:16
  • 1
    @DmitriyPopov I put something as an answer. Hopefully that helps. – dunfa Jan 31 '21 at 18:51

1 Answers1

0

I step away from testcontainers and use Dockercompose instead. This is what I am doing with Gradle for now.

In build.gradle, add

apply from: file('gradle/integration.gradle')

then in the integration.gradle file of gradle folder, add

apply plugin: 'docker-compose'

dockerCompose {
    useComposeFiles = ["${rootDir}/localstack/docker-compose.yml"]
}

and define a task of integrationTest

task integrationTest (type: Test) { 
 //....
}

With the appropriate docker-compose.yml including a Localstack docker image, and other contents in the gradle files, I can run this successfully

./gradlew clean integrationTest
dunfa
  • 509
  • 1
  • 10
  • 19