DEV Community

kaan karakoc
kaan karakoc

Posted on

I Turned My 2-Year-Old GitHub PR into This Blog Post (Using My Own Tool)

We've all been there. You spend hours, maybe even days, crafting the perfect Pull Request for an open-source project. You document everything, polish the code, and finally see that satisfying purple "Merged" badge.

Fast forward a year or two, and that contribution is just a ghost in your commit history, its story untold.

I recently found one of my own forgotten PRs from about two years ago where I added a new module to the testcontainers-node library. (https://github.com/testcontainers/testcontainers-node/pull/646) I wanted to tell the story of that contribution, but I didn't want to spend hours writing it from scratch.

So, I decided to "eat my own dog food."

The core technical deep-dive of this article was generated in about 30 seconds using my own tool, DevToPost.

Here's a quick demo of the tool in action:
https://www.loom.com/share/d72bae1f4b944fee9fe2946e2833d5ea?sid=69e16458-137e-40f4-abf0-a1ee7306013c


The Story of the Contribution (Generated by DevToPost)


Streamlining Couchbase Integration Testing with the New Testcontainers-Node Module

We are pleased to announce the addition of a dedicated Couchbase module to Testcontainers-Node. This new module empowers developers to effortlessly spin up Couchbase instances as part of their integration tests, significantly simplifying the setup and teardown of complex database environments. For anyone building applications with Couchbase, this enhancement brings a new level of efficiency and reliability to the testing workflow.

The Challenge of Integrating Couchbase in Tests

Integrating a robust NoSQL database like Couchbase into automated testing environments can often be a source of friction. Developers face several challenges:

  • Setup Complexity: Couchbase is a distributed document database with various services (Key-Value, Query, Search, Analytics, Eventing) and configuration options for buckets, users, and memory quotas. Manually setting up a consistent Couchbase instance for each test run can be time-consuming and error-prone.
  • Environmental Parity: Ensuring that the test environment closely mirrors production can be difficult, leading to discrepancies and unreliable test results.
  • Isolation: Tests need to run in isolation to prevent side effects and ensure reproducibility. Managing separate Couchbase instances for parallel tests typically requires sophisticated orchestration.
  • Resource Management: Efficiently starting and stopping database instances, and cleaning up resources after tests, is crucial to maintain fast feedback cycles and avoid resource leakage.

These challenges often lead to developers resorting to slower, less reliable alternatives or even skipping integration tests altogether, which can compromise application quality.

Introducing the Couchbase Module for Testcontainers-Node

The new Couchbase module for Testcontainers-Node directly addresses these pain points by providing a programmatic, Docker-native way to manage Couchbase instances for testing. Leveraging the power of Testcontainers, developers can now define their Couchbase test environment in code, guaranteeing consistency and isolation.

The core of this new functionality resides in the CouchbaseContainer class, which extends GenericContainer and provides Couchbase-specific configuration methods.

Key features and capabilities include:

  • Automatic Lifecycle Management: CouchbaseContainer handles the entire lifecycle of a Couchbase Docker container, from pulling the image to starting it, waiting for it to be ready, and stopping it upon test completion.
  • Flexible Service Configuration: Developers can specify which Couchbase services (KV, Query, Search, Index, Analytics, Eventing) should be enabled for their test instance, tailoring the environment precisely to their needs. The module intelligently validates service compatibility with Couchbase editions (e.g., Analytics and Eventing services require an Enterprise image).
  • Customizable Buckets: The BucketDefinition class allows for detailed configuration of Couchbase buckets, including name, RAM quota, number of replicas, flush enablement, and primary index creation.
  • Credential Management: Easily configure administrative usernames and passwords for the Couchbase instance.
  • Support for Editions: Seamlessly switch between Couchbase Community and Enterprise editions by simply specifying the Docker image tag.

Getting Started: Installation and Basic Usage

To begin using the Couchbase module, install it as a development dependency in your project:

npm install @testcontainers/couchbase --save-dev
Enter fullscreen mode Exit fullscreen mode

Once installed, you can integrate a Couchbase container into your tests with minimal effort. The following example demonstrates how to start a Couchbase container, create a bucket, connect to it using the Couchbase Node.js SDK, and perform a basic key-value operation.

Here's a concise example of initializing a Couchbase container and interacting with it:

import { CouchbaseContainer, BucketDefinition } from "@testcontainers/couchbase";
import { Cluster } from "couchbase"; // Assumes the couchbase SDK is installed

async function runCouchbaseExample() {
  const COUCHBASE_IMAGE = "couchbase/server:community-7.0.2";
  const bucketDefinition = new BucketDefinition("mytestbucket");

  // Initialize and start the Couchbase container
  const container = await new CouchbaseContainer(COUCHBASE_IMAGE)
    .withBucket(bucketDefinition)
    .withCredentials("testuser", "testpassword") // Custom credentials
    .start();

  console.log("Couchbase container started and ready.");

  // Connect to the Couchbase cluster using the provided connection string and credentials
  const cluster = new Cluster(container.getConnectionString(), {
    username: container.getUsername(),
    password: container.getPassword(),
  });

  const bucket = cluster.bucket(bucketDefinition.getName());
  const collection = bucket.defaultCollection();

  // Perform a simple upsert and get operation
  await collection.upsert("my_document_id", { message: "Hello from Testcontainers!" });
  const result = await collection.get("my_document_id");
  console.log("Retrieved document:", result.content);

  // Clean up: close the cluster connection and stop the container
  await cluster.close();
  await container.stop();
  console.log("Couchbase container stopped.");
}

runCouchbaseExample().catch(console.error);
Enter fullscreen mode Exit fullscreen mode

This example demonstrates the simplicity of the API: instantiate CouchbaseContainer, configure it with buckets and credentials, and call .start(). Testcontainers handles the rest, providing you with a ready-to-use Couchbase instance.

Advanced Configuration Options

The module provides extensive configuration options to precisely match your testing needs. You can enable specific Couchbase services, set custom memory quotas for those services, and fine-tune bucket properties.

For instance, if your application requires Couchbase's Analytics service, you would use an Enterprise image and explicitly enable the service:

import { CouchbaseContainer, BucketDefinition, CouchbaseService } from "@testcontainers/couchbase";
import { Cluster } from "couchbase";

async function runAdvancedCouchbaseExample() {
  // Note: Analytics service requires an Enterprise image
  const COUCHBASE_IMAGE_ENTERPRISE = "couchbase/server:enterprise-7.0.3";
  const analyticsBucket = new BucketDefinition("analyticsData")
    .withFlushEnabled(true) // Enable flush for easy cleanup
    .withPrimaryIndex(true) // Create a primary index for querying
    .withQuota(200); // Set a custom RAM quota for the bucket (MB)

  const container = await new CouchbaseContainer(COUCHBASE_IMAGE_ENTERPRISE)
    .withBucket(analyticsBucket)
    .withEnabledServices(
      CouchbaseService.KV,
      CouchbaseService.QUERY,
      CouchbaseService.ANALYTICS // Enable the Analytics service
    )
    .withServiceQuota(CouchbaseService.ANALYTICS, 1200) // Set a custom RAM quota for the Analytics service (MB)
    .withCredentials("admin", "secretpassword")
    .start();

  console.log("Couchbase Enterprise container with Analytics service started.");

  const cluster = new Cluster(container.getConnectionString(), {
    username: container.getUsername(),
    password: container.getPassword(),
  });
  // ... perform advanced operations with the Analytics service ...

  await cluster.close();
  await container.stop();
  console.log("Advanced Couchbase container stopped.");
}

runAdvancedCouchbaseExample().catch(console.error);
Enter fullscreen mode Exit fullscreen mode

This snippet illustrates how to provision a Couchbase Enterprise instance with the Analytics service enabled, set its memory quota, and configure a bucket with flush capabilities and a primary index, all within your test code.

Under the Hood: A Glimpse at the Setup Process

The CouchbaseContainer performs a sophisticated orchestration to bring up a fully configured Couchbase instance. Upon start(), it:

  1. Exposes Necessary Ports: Dynamically determines and exposes the required Couchbase service ports.
  2. Waits for Node Online: Waits for the Couchbase node to become accessible via its management API.
  3. Determines Edition: Checks if the running image is an Enterprise or Community edition to validate service requests.
  4. Renames Node: Configures the Couchbase node to use its correct network hostname.
  5. Initializes Services: Enables the specified services (KV, Query, Search, etc.).
  6. Sets Memory Quotas: Configures memory quotas for each enabled service, respecting custom settings or default minimums.
  7. Configures Admin User: Sets up the administrative user credentials.
  8. Maps External Ports: Ensures that external ports are correctly mapped for client connectivity.
  9. Configures Indexer: Sets the storage mode for the indexer service.
  10. Creates Buckets and Indexes: Iterates through all defined BucketDefinition objects, creating each bucket with its specified properties and, if requested, a primary index. Robust wait strategies ensure that buckets and indexes are fully operational before proceeding.

This intricate sequence, abstracted away from the developer, ensures a stable and ready-to-use Couchbase instance for every test.

Impact and Benefits

The introduction of the Couchbase module delivers significant benefits to developers:

  • Reproducible and Isolated Environments: Each test can run with its own dedicated Couchbase instance, ensuring that tests are truly isolated and results are consistent across different machines and runs.
  • Faster Feedback Loops: Eliminating manual setup and teardown of Couchbase instances accelerates test execution, providing quicker feedback on code changes.
  • Simplified Test Code: Reduces boilerplate in test suites by abstracting away the complexities of Couchbase provisioning and configuration.
  • Enhanced Test Reliability: By accurately simulating a Couchbase environment, integration tests become more reliable and better reflect real-world scenarios.
  • Comprehensive Configuration: Supports a wide range of Couchbase configurations, from basic Key-Value stores to multi-service clusters, enabling thorough testing of complex application interactions.

Conclusion

The new Couchbase module for Testcontainers-Node marks a significant step forward in simplifying the integration testing of applications that leverage Couchbase. By allowing developers to define and manage their Couchbase test environments as code, it promotes better testing practices, reduces development overhead, and ultimately leads to more robust and reliable software. We encourage all Couchbase developers to explore this powerful new tool and experience the benefits of streamlined, container-backed integration testing.

Top comments (0)