Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Better Testing with Testcontainers

Better Testing with Testcontainers

Early in the process of writing applications, you have to decide how to run your integration tests. Will they hit a dedicated testing instance of Neo4j that is always running, or will you use the embedded instance?

There is a third option: starting a database before testing and stopping it after testing. Most of the time, this may require some manual scripting around test execution, but there is a much easier method: using testcontainers with a Neo4j module.

In this session, you'll learn how to start new instances of the database along with your tests whenever needed, take control of the lifetime of the container, and expose connection information needed for testing. With this, your testing infrastructure becomes much more portable and there is no need to share credentials of the test server in the future.

Gerrit Meier

November 17, 2022
Tweet

More Decks by Gerrit Meier

Other Decks in Programming

Transcript

  1. 1
    Better Testing With Testcontainers

    View Slide

  2. 2
    Gerrit Meier
    ● Software Engineer at Neo4j
    ● Team Spring Data Neo4j and Neo4j OGM

    View Slide

  3. Software Testing

    View Slide

  4. Software Testing Integration testing

    View Slide

  5. Integration testing
    “Testing our application interacting
    with an external system.”
    -me

    View Slide

  6. Common setups
    6

    View Slide

  7. Common setups
    7
    Embedded

    View Slide

  8. Common setups
    8
    Embedded Local instance

    View Slide

  9. Common setups
    9
    Embedded Local instance
    Remote instance

    View Slide

  10. Common setups
    10
    Embedded Local instance
    Remote instance Docker

    View Slide

  11. 11
    Embedded

    View Slide

  12. 12
    Pros
    - Clean instance
    - Portable test suite
    - No side-effects from the
    outside
    Embedded

    View Slide

  13. 13
    Cons
    Pros
    - Dependency version
    - JVM version
    - Non-existing network
    - Memory allocation
    - Post-mortem debugging
    - Clean instance
    - Portable test suite
    - No side-effects from the
    outside
    Embedded

    View Slide

  14. 14
    Local instance

    View Slide

  15. 15
    Pros
    - Separation of application and
    database
    - Network
    - No version conflicts
    - Post-mortem debugging
    Local instance

    View Slide

  16. 16
    Cons
    Pros
    - Custom configuration
    - Setup needed for colleagues
    and CI
    - Lifecycle control on CI
    - Separation of application and
    database
    - Network
    - No version conflicts
    - Post-mortem debugging
    Local instance

    View Slide

  17. 17
    Remote instance a.k.a. the “Test instance”

    View Slide

  18. 18
    Pros
    - One time setup
    - Always on
    - No lifecycle management
    needed
    Remote instance a.k.a. the “Test instance”

    View Slide

  19. 19
    Cons
    Pros
    - Shared database state
    - Conflicting tests
    - Unexpected test failures
    - Post-mortem debugging
    - One time setup
    - Always on
    - No lifecycle management
    needed
    Remote instance a.k.a. the “Test instance”

    View Slide

  20. 20
    Docker

    View Slide

  21. 21
    Pros
    - Orchestration within test
    setup
    - Isolated testing
    Docker

    View Slide

  22. 22
    Cons
    Pros
    - Java API
    “command line”-style
    - Ready(?)
    - Orchestration within test
    setup
    - Isolated testing
    Docker

    View Slide

  23. Testcontainers
    - Fluent wrapper around Docker API
    - Support for a lot of systems out-of-the-box
    - Aware of readiness(!)
    - Seamless integration with JUnit (and Spock)
    - Networks of multiple containers
    - Copy data for testing

    View Slide

  24. 24
    Spring Boot
    Spring Data Neo4j
    JUnit 5

    View Slide

  25. 25
    MaterialIT.java @SpringBootTest
    class MaterialIT {
    private final MaterialRepository repository;
    private final Neo4jClient neo4jClient;
    @Autowired
    MaterialIT(MaterialRepository repository,
    Neo4jClient neo4jClient) {
    this.repository = repository;
    this.neo4jClient = neo4jClient;
    }
    }

    View Slide

  26. 26
    pom.xml
    Add Testcontainers

    org.testcontainers
    junit-jupiter
    1.17.6
    test

    View Slide

  27. 27
    MaterialIT.java @SpringBootTest
    class MaterialIT {
    static GenericContainer> container =
    new GenericContainer<>("neo4j:5")
    .withExposedPorts(7687)
    .withEnv("NEO4J_AUTH", "neo4j/verysecret");
    }

    View Slide

  28. 28
    MaterialIT.java @SpringBootTest
    class MaterialIT {
    static GenericContainer> container =
    new GenericContainer<>("neo4j:5")
    .withExposedPorts(7687)
    .withEnv("NEO4J_AUTH", "neo4j/verysecret");
    @BeforeAll
    static void startDatabase() {
    container.start();
    }
    }

    View Slide

  29. 29
    MaterialIT
    #testingProperties
    @DynamicPropertySource
    static void testingProperties(
    DynamicPropertyRegistry registry) {
    registry.add("spring.neo4j.uri",
    () ->
    "neo4j://" + container.getHost() +
    ":" + container.getMappedPort(7687));
    registry.add("spring.neo4j.authentication.username",
    () -> "neo4j");
    registry.add("spring.neo4j.authentication.password",
    () -> "verysecret");
    }

    View Slide

  30. 30
    MaterialIT.java @SpringBootTest
    @Testcontainers
    class MaterialIT {
    @Container
    static GenericContainer> container =
    new GenericContainer<>("neo4j:5")
    .withExposedPorts(7687)
    .withEnv("NEO4J_AUTH", "neo4j/verysecret");
    }

    View Slide

  31. Testcontainers Neo4j
    - Neo4j readiness aware
    - Simplified env parameters for configuration
    - Exposes Bolt and HTTP port as default
    - Neo4j specific API
    - Admin user setup
    - Connection related information
    - Neo4j Labs Plugin activation

    View Slide

  32. 32
    pom.xml
    Add Testcontainers

    org.testcontainers
    neo4j
    1.17.6
    test

    View Slide

  33. 33
    MaterialIT.java @SpringBootTest
    @Testcontainers
    class MaterialIT {
    @Container
    static Neo4jContainer> container =
    new Neo4jContainer<>("neo4j:5")
    .withAdminPassword("verysecret")
    }

    View Slide

  34. 34
    MaterialIT
    #testingProperties
    @DynamicPropertySource
    static void testingProperties(
    DynamicPropertyRegistry registry) {
    registry.add("spring.neo4j.uri",
    container::getBoltUrl);
    registry.add("spring.neo4j.authentication.username",
    () -> "neo4j");
    registry.add("spring.neo4j.authentication.password",
    container::getAdminPassword);
    }

    View Slide

  35. 35
    MaterialIT
    #pluginCall
    void pluginCall() {
    String apocVersion = neo4jClient
    .query("RETURN apoc.version()")
    .fetchAs(String.class)
    .one().get();
    Assertions.assertThat(apocVersion)
    .isEqualTo("5.1.0");
    }

    View Slide

  36. 36
    MaterialIT
    #pluginCall
    void pluginCall() {
    String apocVersion = neo4jClient
    .query("RETURN apoc.version()")
    .fetchAs(String.class)
    .one().get();
    Assertions.assertThat(apocVersion)
    .isEqualTo("5.1.0");
    }
    Unknown function 'apoc.version'

    View Slide

  37. 37
    MaterialIT.java @SpringBootTest
    @Testcontainers
    class MaterialIT {
    @Container
    static Neo4jContainer> container =
    new Neo4jContainer<>("neo4j:5")
    .withAdminPassword("verysecret")
    .withLabsPlugins(Neo4jLabsPlugin.APOC)
    }

    View Slide

  38. 38
    MaterialIT
    #pluginCall
    void pluginCall() {
    String apocVersion = neo4jClient
    .query("RETURN apoc.version()")
    .fetchAs(String.class)
    .one().get();
    Assertions.assertThat(apocVersion)
    .isEqualTo("5.1.0");
    }

    View Slide

  39. 39
    MaterialIT
    #pluginCall
    void pluginCall() {
    String apocVersion = neo4jClient
    .query("RETURN apoc.version()")
    .fetchAs(String.class)
    .one().get();
    Assertions.assertThat(apocVersion)
    .isEqualTo("5.1.0");
    }

    View Slide

  40. Performance
    (experimental) Feature: Reusable Testcontainers

    View Slide

  41. Performance
    (experimental) Feature: Reusable Testcontainers
    Neo4jContainer> container = new Neo4jContainer<>("neo4j:5")
    .withAdminPassword("verysecret")
    .withReuse(true)

    View Slide

  42. Performance
    (experimental) Feature: Reusable Testcontainers
    testcontainers.reuse.enable=true
    ~/.testcontainers.properties
    Neo4jContainer> container = new Neo4jContainer<>("neo4j:5")
    .withAdminPassword("verysecret")
    .withReuse(true)

    View Slide

  43. Conclusion
    ● Reproducible and portable tests
    ● No manual lifecycle management (JUnit extension)
    ● For more performance: reusable containers
    ● Covers more than just Neo4j
    ● Create Test Matrix with ease

    View Slide

  44. 44
    Create data Neo4j-Migrations
    Neo4j Migrations: The Lean Way of Applying Database
    Refactorings to Neo4j
    Michael Simons
    15:40 (UTC)
    16:40 (CET)

    View Slide

  45. 45
    ● Testcontainers:
    https://www.testcontainers.org/
    ● Reusable Testcontainers:
    ● https://foojay.io/today/faster-integration-tests-
    with-reusable-testcontainers/
    ● me:
    @meistermeier(@mastodon.social)
    Thank
    you!

    View Slide