{"id":18573137,"url":"https://github.com/localstack-samples/sample-gradle-pulumi-testcontainers","last_synced_at":"2026-01-27T15:04:57.719Z","repository":{"id":252767127,"uuid":"838707325","full_name":"localstack-samples/sample-gradle-pulumi-testcontainers","owner":"localstack-samples","description":"Demo application using Gradle to create an AWS stack, run the application and tests.","archived":false,"fork":false,"pushed_at":"2024-08-12T17:46:08.000Z","size":110,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-05-20T22:34:53.801Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/localstack-samples.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-08-06T07:25:46.000Z","updated_at":"2025-05-01T22:50:37.000Z","dependencies_parsed_at":"2024-08-12T11:44:13.590Z","dependency_job_id":"e610224d-d117-41df-8b6b-f8b403a532fb","html_url":"https://github.com/localstack-samples/sample-gradle-pulumi-testcontainers","commit_stats":null,"previous_names":["localstack-samples/sample-gradle-pulumi-testcontainers"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/localstack-samples/sample-gradle-pulumi-testcontainers","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/localstack-samples%2Fsample-gradle-pulumi-testcontainers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/localstack-samples%2Fsample-gradle-pulumi-testcontainers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/localstack-samples%2Fsample-gradle-pulumi-testcontainers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/localstack-samples%2Fsample-gradle-pulumi-testcontainers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/localstack-samples","download_url":"https://codeload.github.com/localstack-samples/sample-gradle-pulumi-testcontainers/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/localstack-samples%2Fsample-gradle-pulumi-testcontainers/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28815385,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-27T12:25:15.069Z","status":"ssl_error","status_checked_at":"2026-01-27T12:25:05.297Z","response_time":168,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-06T23:08:07.948Z","updated_at":"2026-01-27T15:04:57.679Z","avatar_url":"https://github.com/localstack-samples.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n# Running \u0026 Testing a Full Backend Application Using Gradle and LocalStack\n\nBased on [@oleg-nenashev](https://github.com/oleg-nenashev)'s [mocks-as-code-demo](https://github.com/oleg-nenashev/mocks-as-code-demo) project.\n\n\n## Introduction\n\nIn modern software development, it's common to juggle multiple tools to manage, build, and test backend applications. \nHowever, achieving a streamlined workflow is critical for maintaining productivity and reducing friction. \nIn this post, we'll explore how to use Gradle as the central orchestrator for building and running a Spring Boot application, creating infrastructure with Pulumi for Java, and testing using LocalStack. \nWe'll focus on how Gradle and LocalStack work together to provide a seamless experience from local development to cloud deployment.\n\n## Prerequisites\n\nMake sure you have the following installed:\n- Java 17+\n- A [compatible Docker environment](https://www.testcontainers.org/supported_docker_environment/)\n- Gradle\n- `pulumi` \u0026 `pulumilocal`\n\n## Setting Up the Project\n\nTo begin, let's set up our project structure. \nWe'll use Gradle to manage dependencies, tasks, and the overall build process.\nThe key components include:\n\n- Spring Boot Application: This serves as our backend service.\n- Pulumi for Java: Used to define and create AWS infrastructure.\n- LocalStack with Testcontainers: A framework providing a fully functional local AWS cloud stack that allows us to emulate AWS services during testing.\n\nThe `build.gradle` file will include the necessary dependencies for Spring Boot, Pulumi, LocalStack, and Testcontainers.\n\n## Creating Infrastructure with Pulumi\n\nPulumi allows us to define and manage cloud infrastructure using familiar programming languages. \nIn this project, we'll use Pulumi for Java to create necessary AWS resources, such as an S3 bucket and an SQS queue. \nThese resources will be provisioned locally using LocalStack during the testing phase.\n\nLet's define the infrastructure using Pulumi within a dedicated Gradle task. \nThis task will automate the creation of the infrastructure individually, on demand, or every time we run our application, by chaining the tasks. \nWe have two options here, defining a task for local resources and telling our application what endpoints to use, and a second task for real AWS resources and defaulting to our preconfigured clients.\n\n```groovy\ntask initStack(type: Exec) {\n    commandLine 'pulumilocal', 'stack', 'init', 'dev'\n}\n\ntask createStack(type: Exec) {\n    commandLine 'pulumilocal', 'up', '--yes', '--stack', 'dev'\n}\n```\n\nThe [`pulumilocal`](https://github.com/localstack/pulumi-local) command is a thin wrapper around the `pulumi` command line interface to use Pulumi with LocalStack.\nThese tasks will pair with the `local` profile where we configure the LocalStack endpoints:\n\n```groovy\ntask runSpringBootAppWithLocalStack(type: JavaExec) {\n    description = 'Run the Spring Boot application'\n    classpath = sourceSets.main.runtimeClasspath\n    mainClass = 'app.Application'\n    args = ['--spring.profiles.active=local']\n}\n```\n\nOur endpoints will be defined as such in the `application-local.properties` file:\n\n```properties\nspring.cloud.aws.endpoint=http://localhost.localstack.cloud:4566\nspring.cloud.aws.s3.endpoint=http://s3.localhost.localstack.cloud:4566\n```\n\nIf we want to run our application using the AWS cloud platform, our options are as following:\n\n```groovy\ntask initStack(type: Exec) {\n    commandLine 'pulumi', 'stack', 'init', 'dev'\n}\n\ntask createStack(type: Exec) {\n    commandLine 'pulumi', 'up', '--yes', '--stack', 'dev'\n}\n```\n\nAnd our application will start with the default configurations:\n\n```groovy\ntask runSpringBootApp(type: JavaExec) {\n    description = 'Run the Spring Boot application'\n    classpath = sourceSets.main.runtimeClasspath\n    mainClass = 'app.Application'\n}\n```\n\nThe ApplicationStack class will contain the Pulumi code to define and create the resources:\n\n```java\npublic class ApplicationStack {\n    public static void main(String[] args) {\n\n        Pulumi.run(ApplicationStack::createResources);\n    }\n    private static void createResources(Context ctx) {\n        AwsConfig config = YamlConfigLoader.loadConfig(\"application.yml\");\n\n        var bucket = new Bucket(config.getBucket(),\n                BucketArgs.builder()\n                        .bucket(config.getBucket())\n                        .forceDestroy(true)\n                        .build());\n        ctx.export(\"bucketArn\", bucket.arn());\n\n        var queue = new Queue(config.getQueue() + \".fifo\", QueueArgs.builder()\n                .name(config.getQueue() + \".fifo\")\n                .fifoQueue(true)\n                .contentBasedDeduplication(true)\n                .build());\n\n        ctx.export(\"queueUrl\", queue.url());\n\n    }\n}\n```\nThis setup ensures that every time we run the `createStack` task, our S3 bucket and SQS queue will be provisioned, either on AWS or locally with LocalStack.\n\n## Building and Running the Spring Boot Application\n\nIn a nutshell, the Spring Boot application can be described by the following diagram:\n\n![Diagram](application-diagram.png)\n\nWith our infrastructure in place, the next step is to build and run our Spring Boot application. \nGradle makes this straightforward, handling dependency management, building, and running the application.\nWe have already defined the Gradle task for building and running the application. So depending on your environment you'll use `runSpringBootApp` or `runSpringBootAppWithLocalStack`.\n\n## Testing with LocalStack\n\nTesting is a critical phase where LocalStack proves invaluable. \nLocalStack allows us to emulate AWS services locally, ensuring that our Spring Boot application interacts correctly with the infrastructure before deploying to the real AWS environment.\n\nWe’ll set up our tests to use LocalStack, making sure they interact with the local versions of the AWS services.\nGradle will orchestrate the test execution, tying it all together.\n\nThere are two major focus points of our tests: infrastructure and application. \nThe great thing about Pulumi allowing us to define our infrastructure using one of the most popular programming languages is the possibility to include this process in our test suites, just like any other part of an application.\n\nHere's an example of a test that verifies the existence of the S3 bucket we provisioned:\n\n```java\n@Testcontainers\npublic class IaCStackTest {\n\n    private static final String STACK_NAME = \"dev\";\n\n    @Container\n    public static LocalStackContainer localStack = new LocalStackContainer(DockerImageName.parse(\"localstack/localstack:3.5.0\"))\n            .withServices(LocalStackContainer.Service.S3, LocalStackContainer.Service.SQS);\n\n    static File WORK_DIR;\n\n    private S3Client s3Client;\n\n    static {\n        localStack.start();\n    }\n\n    @Before\n    public void setup() throws IOException {\n        URI lsEndpoint = localStack.getEndpoint();\n        WORK_DIR = new File(\".\");\n        PulumiLocalStackAdapter.configure(localStack, STACK_NAME, WORK_DIR);\n\n        AwsBasicCredentials awsCreds = AwsBasicCredentials.create(\"test\", \"test\");\n\n        s3Client = S3Client.builder()\n                .credentialsProvider(StaticCredentialsProvider.create(awsCreds))\n                .endpointOverride(lsEndpoint)\n                .region(Region.of(\"us-east-1\"))\n                .build();\n    }\n\n\n    @Test\n    public void testBucketCreation() throws IOException, InterruptedException {\n\n        PulumiLocalStackAdapter.init(WORK_DIR);\n\n        PulumiLocalStackAdapter.up(WORK_DIR);\n\n\n        var buckets = s3Client.listBuckets().buckets();\n        assertTrue(buckets.stream().anyMatch(b -\u003e b.name().equals(\"6578-demo-message-bucket\")));\n\n        PulumiLocalStackAdapter.clean(WORK_DIR);\n\n    }\n}\n```\n\nThe `IaCStackTest` class is a test class that uses the Testcontainers framework to run integration tests against AWS services simulated by LocalStack.\nThe class is annotated with `@Testcontainers`, indicating that it uses containerized services for testing.\n\nWithin the class, a static instance of `LocalStackContainer` is created and configured to run LocalStack with S3 and SQS services enabled.\nThe container is started before any tests run, ensuring that the necessary AWS services are available for testing. \n\nThe setup method, annotated with `@Before`, is executed before all the tests. \nIt sets up the S3 client with credentials and points it to the LocalStack endpoint, preparing the environment for testing.\n\nThe `testBucketCreation` method is a test that verifies the creation of an S3 bucket using Pulumi with LocalStack.\nThe method first initializes the Pulumi stack and then runs it to create the infrastructure defined in the Pulumi code.\nAfterward, the test checks whether the specified S3 bucket (\"6578-demo-message-bucket\") exists in the LocalStack environment. \nFinally, the method cleans up the Pulumi stack to reset the environment.\n\nThis class effectively demonstrates how to use Pulumi with LocalStack for testing Infrastructure as Code (IaC) in a controlled and isolated environment.\n\nThe other equally important part of this operation is testing the application's behaviour.\n\n```java\n@Testcontainers\n@SpringBootTest(classes = app.Application.class, \n        webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, properties = {\n        \"server.port=8080\"})\n@RunWith(SpringRunner.class)\n@ExtendWith(SpringExtension.class)\n\npublic class ApplicationTest {\n\n    private final TestRestTemplate restTemplate= new TestRestTemplate();\n\n    private static final String STACK_NAME = \"dev\";\n\n    static File WORK_DIR;\n\n    @Container\n    public static LocalStackContainer localStack = new LocalStackContainer(DockerImageName.parse(\"localstack/localstack:3.5.0\"))\n            .withServices(LocalStackContainer.Service.S3, LocalStackContainer.Service.SQS);\n\n    static {\n        localStack.start();\n    }\n\n    @DynamicPropertySource\n    static void dynamicProperties(DynamicPropertyRegistry registry) {\n        registry.add(\"spring.cloud.aws.endpoint\", () -\u003e localStack.getEndpoint());\n        registry.add(\"spring.cloud.aws.s3.endpoint\", () -\u003e localStack.getEndpoint());\n    }\n    @Before\n    public void setup() throws IOException, InterruptedException {\n        URI lsEndpoint = localStack.getEndpoint();\n        WORK_DIR = new File(\".\");\n        PulumiLocalStackAdapter.configure(localStack, STACK_NAME, WORK_DIR);\n\n        PulumiLocalStackAdapter.init(WORK_DIR);\n\n        PulumiLocalStackAdapter.up(WORK_DIR);\n    }\n\n    @After\n    public void tearDown() throws IOException, InterruptedException {\n        PulumiLocalStackAdapter.clean(WORK_DIR);\n    }\n\n    @Test\n    public void testPostAndGetMessage() throws IOException, InterruptedException {\n        // Create a unique UUID and the message content\n        UUID uuid = UUID.randomUUID();\n        String content = \"Hello, World!\";\n\n        // Define the message object\n        Message message = new Message(uuid, content);\n\n        // Define the URL for the POST request\n        String postUrl = \"http://localhost:8080/api/messages\";\n\n        // Send the POST request to save the message\n        HttpHeaders headers = new HttpHeaders();\n        headers.setContentType(MediaType.APPLICATION_JSON);\n        HttpEntity\u003cMessage\u003e request = new HttpEntity\u003c\u003e(message, headers);\n        ResponseEntity\u003cString\u003e postResponse = restTemplate.exchange(postUrl, HttpMethod.POST, request, String.class);\n\n        // Assert that the POST request was successful (HTTP 201 Created)\n        assertThat(postResponse.getStatusCode().is2xxSuccessful()).isTrue();\n\n        // Define the URL for the GET request\n        String getUrl = \"http://localhost:8080/api/messages/\" + uuid;\n\n        // Send the GET request to retrieve the message\n        ResponseEntity\u003cjava.util.Map\u003e getResponse = restTemplate.exchange(getUrl, HttpMethod.GET, null, java.util.Map.class);\n\n        // Assert that the GET request was successful and the content matches\n        assertThat(getResponse.getStatusCode().is2xxSuccessful()).isTrue();\n        assertThat(getResponse.getBody().get(\"content\")).isEqualTo(content);\n    }\n}\n```\n\nThe `ApplicationTest` class is a comprehensive test class that integrates Spring Boot with Testcontainers and LocalStack to test the functionality of a backend application in an AWS environment.\nThis class is annotated with `@SpringBootTest`, meaning it will load the full application context for testing, and it runs on a defined port, ensuring that the web environment is set up correctly.\n\nThe class utilizes `TestRestTemplate` for making HTTP requests to the application, mimicking client behavior.\nA static LocalStackContainer is set up to emulate AWS services.\nThe infrastructure is initialized and provisioned using Pulumi before the tests, ensuring that the necessary resources are available.\n\nThe `testPostAndGetMessage` method is a core test that verifies the application's ability to handle POST and GET requests for messages. \nIt simulates the complete flow of creating a message via a POST request and retrieving it via a GET request, asserting that the operations are successful and the data is correctly handled.\n\nThis test demonstrates how to effectively test a Spring Boot application in a controlled environment that closely mirrors production, leveraging the power of Spring Boot, Pulumi, and LocalStack together.\n\nAll of these fall under a singular command: `gradle test`.\n\n## Bringing It All Together\n\nBy integrating Gradle with Pulumi and LocalStack, we create a robust development and testing pipeline. \nEach component plays a crucial role:\n\n- Gradle: Orchestrates tasks, managing dependencies, building, and running the application.\n- Pulumi: Manages infrastructure as code, seamlessly integrated into the build process.\n- LocalStack: Emulates AWS services for local testing and development, ensuring consistency before deployment.\n\nThis streamlined process reduces friction, accelerates development, and ensures a smooth transition from local development to production deployment.\n\n## Conclusion\n\nBy leveraging Gradle, Pulumi, and LocalStack together, we create a powerful and efficient workflow for backend development.\nThis approach not only simplifies the management of complex tasks but also ensures reliable testing in an environment that mirrors production. \nWith this setup, you can confidently build, run, and test your applications, knowing they will perform as expected when deployed to the cloud.\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flocalstack-samples%2Fsample-gradle-pulumi-testcontainers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flocalstack-samples%2Fsample-gradle-pulumi-testcontainers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flocalstack-samples%2Fsample-gradle-pulumi-testcontainers/lists"}