Spring Boot, GraphQL, TestContainers, PostgreSQL: Minimal Setup

The purpose of this flash tip is to provide a minimalistic quickstart code for setting up a module using 

  • Spring Boot 
  • GraphQL
  • PostgreSQL
  • TestContainers

The code is working, and the test is green, but as you can imagine, the logic is very trivial because it is supposed to provide only a starting point.

The code is available here on GitHub. Hereafter, I will be referring to that.

Maven Project definition

The pom.xml includes the required dependencies. Note the following, which I often find very useful

  • dependencies versions, are declared as properties. In that way, we can manage them at a single, central point.
  • The release plugin triggers, through a dedicated profile, the build and publishing of the module Docker image. At the end of the release, there will be the artifact in the Maven repository and the Docker image in the container registry, both versioned. Note it is commented on because the code doesn’t include the credentials for reading/writing a container registry.
  •  The build-and-publish profile can also be triggered standalone: in this way, Maven will publish a SNAPSHOT Docker image.
				
					        <profile>
            <id>build-and-publish-images</id>
            <build>
                <plugins>
                    ...
                    <plugin>
                        <groupId>org.codehaus.mojo</groupId>
                        <artifactId>exec-maven-plugin</artifactId>
                        <version>3.0.0</version>
                        <executions>
                            <execution>
                                <id>docker-build</id>
                                ...
                            </execution>
                            <execution>
                                <id>docker-login</id>
                                ...
                            </execution>
                            <execution>
                                <id>docker-push</id>
                                ...
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
				
			

GraphQL Schema

The GraphQL schema is in /src/main/resources/graphql/schema.graphqls. Spring Boot automatically reads it. If your domain is huge, it can be split into multiple files. Indeed, that is not the case where we have just one type and one query field.

JPA Entity and Spring Data Repository

The domain model consists of a single, simple entity, a Bass; beside the entity, the corresponding Spring Data Repository automatically provides the common data access patterns. 

Note the usage of Lombok, a great tool for reducing the code boilerplate. 

Controller

The API endpoint is a Spring Controller, which maps the schema’s query field(s) and provides the corresponding implementation.  

				
					@Controller
public class ShopController {
    ...
}
				
			

Test: ApplicationInitializer

The ApplicationInitializer is a Spring callback at the beginning of the test suite. The module uses TestContainers to spin up the required middleware used in the tests, PostgreSQL.

				
					public class MiddlewareSetup implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    public static DockerComposeContainer SUBSYSTEMS;

    @Override
    public void initialize(ConfigurableApplicationContext context) {
        if (SUBSYSTEMS == null) {
            var dockerCompose = new File(System.getProperty("user.dir"), "src/test/resources/docker/docker-compose.yml");
            SUBSYSTEMS =
                    new DockerComposeContainer(dockerCompose)
                            .withExposedService(
                                    "postgres_1",
                                    5432,
                                    Wait.forListeningPort().withStartupTimeout(Duration.ofMinutes(5)));
            SUBSYSTEMS.start();
        }
   }
}
				
			

Test: Docker compose

Note the volume definition: it uses a script for initializing and prepopulating the test database. 

				
					version: '3.3'

services:
  postgres:
    image: library/postgres:14.5
    environment:
      - POSTGRES_USER=shop_owner
      - POSTGRES_PASSWORD=029348hdhj
      - POSTGRES_DB=bass_shop
    command: postgres -c max_connections=300 -c log_min_messages=LOG
    volumes:
      - ./populate_shop.sql:/docker-entrypoint-initdb.d/populate_shop.sql
    ports:
      - "5432:5432"
    tty: true
				
			

Test: Superclass

The IntegrationTestCaseSuperLayer includes annotations and “autowired” components we need in all test cases.

				
					@SpringBootTest(
        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
        classes = ElectricBassShop.class)
@ContextConfiguration(initializers = MiddlewareSetup.class)
@AutoConfigureMockMvc
@AutoConfigureGraphQlTester
@ActiveProfiles("it")
public abstract class IntegrationTestCaseSuperLayer {

    @Autowired
    protected HttpGraphQlTester tester;

    @Autowired
    protected MockMvc mvc;
}
				
			

Test Case

And finally, here’s our trivial test case.

				
					public class ShopTestCase extends IntegrationTestCaseSuperLayer {
    @Test
    void findSomeBasses() {
        String query =
                "query {" +
                "  instruments {" +
                "      id" +
                "      brand" +
                "      model" +
                "      fretless" +
                "      strings" +
                "  }" +
                "}";
        var basses =
                tester.document(query)
                        .execute()
                        .path("data.instruments[*]")
                        .entityList(Bass.class)
                        .get();
        assertThat(basses).asList().hasSize(3);

        // Not so robust/complete as test...
        var fenderJazz =
                of(basses.iterator())
                        .map(Iterator::next)
                        .map(Bass.class::cast)
                        .orElseThrow();
        assertThat(fenderJazz.getStrings()).isEqualTo(4);
        assertThat(fenderJazz.getModel()).isEqualTo("Jazz Vintage 62");
        assertThat(fenderJazz.getBrand()).isEqualTo("Fender");
        assertThat(fenderJazz.isFretless()).isFalse();
    }
}
				
			

Share this post

Leave a Reply