skip to Main Content

I’d like to use testcontainers for integration testing. I need to test against a clickhouse storage.

The docker image is yandex/clichouse-server

My code thus far (imported mostly from the official redis example on testcontainers website):

    ctx := context.Background()
    req := testcontainers.ContainerRequest{
        Image: "yandex/clickhouse-server",
        ExposedPorts: []string{"9000/tcp"},
    }
    chContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
        ContainerRequest: req,
        Started:          true,
    })
    require.NoError(t, err, "unexpected error while creating clickhouse container")

    endpoint, err := chContainer.Endpoint(ctx, "")
    require.NoError(t, err)

This throws an error port not found on getting the endpoint, and I’m not sure where to go from there.

2

Answers


  1. Chosen as BEST ANSWER

    Here is what I got working after trial / errors:

    const (
        dbName       = "crazy"
        fakeUser     = "jondoe"
        fakePassword = "bond girl"
    )
    
    // NewTestClickhouseDB spins up a new clickhouse container database
    func NewTestClickhouseDB(t *testing.T) *sql.DB {
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
        t.Cleanup(cancel)
        req := testcontainers.ContainerRequest{
            Image: "clickhouse/clickhouse-server",
            Env: map[string]string{
                "CLICKHOUSE_DB":       dbName,
                "CLICKHOUSE_USER":     fakeUser,
                "CLICKHOUSE_PASSWORD": fakePassword,
            },
    
            ExposedPorts: []string{"9000/tcp"},
            WaitingFor:   wait.ForListeningPort("9000"),
        }
        chContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
            ContainerRequest: req,
            Started:          true,
        })
        require.NoError(t, err, "unexpected error while creating clickhouse container")
    
        p, err := chContainer.MappedPort(ctx, "9000")
        require.NoError(t, err, "expected mapped port to be found on clickhouse container")
    
        addr := fmt.Sprintf("127.0.0.1:%d", p.Int())
        conn := clickhouse.OpenDB(&clickhouse.Options{
            Addr: []string{addr},
            Auth: clickhouse.Auth{
                Database: dbName,
                Username: fakeUser,
                Password: fakePassword,
            },
        })
    
        for {
            if ctx.Err() != nil {
                t.Fatalf("time/out: ping db failed for 10seconds")
            }
            err := conn.Ping()
            if err != nil {
                time.Sleep(10 * time.Millisecond)
                continue
            }
            break
        }
    
        return conn
    }
    
    

    This spins up a clickhouse container and returns the sql.Db or t/o after 10 seconds.


  2. Have you tried using the wait APIs in Testcontainers Go? https://github.com/testcontainers/testcontainers-go/tree/main/wait

    With them you’ll be able to wait for multiple things (even at the same time):

    • a log entry
    • a port to be ready
    • a SQL query
    • an HTTP request
    • an exit code after running a program in the container

    You can find useful examples in the repository. I.e., and example for a log entry:

    ctx := context.Background()
        req := ContainerRequest{
            Image:        "docker.io/mysql:latest",
            ExposedPorts: []string{"3306/tcp", "33060/tcp"},
            Env: map[string]string{
                "MYSQL_ROOT_PASSWORD": "password",
                "MYSQL_DATABASE":      "database",
            },
            WaitingFor: wait.ForLog("test context timeout").WithStartupTimeout(1 * time.Second),
        }
        _, err := GenericContainer(ctx, GenericContainerRequest{
            ProviderType:     providerType,
            ContainerRequest: req,
            Started:          true,
        })
    

    EDIT: a more elaborated example, including a wait strategy using HTTP requests would be:

    const (
            dbName       = "crazy"
            fakeUser     = "jondoe"
            fakePassword = "bond girl"
        )
    
        ctx := context.Background()
    
        req := ContainerRequest{
            Image: "clickhouse/clickhouse-server",
            Env: map[string]string{
                "CLICKHOUSE_DB":       dbName,
                "CLICKHOUSE_USER":     fakeUser,
                "CLICKHOUSE_PASSWORD": fakePassword,
            },
            ExposedPorts: []string{
                "8123/tcp",
                "9000/tcp",
            },
            WaitingFor: wait.ForAll(
                wait.ForHTTP("/ping").WithPort("8123/tcp").WithStatusCodeMatcher(
                    func(status int) bool {
                        return status == http.StatusOK
                    },
                ),
            ),
        }
    
        clickhouseContainer, err := GenericContainer(ctx, GenericContainerRequest{
            ContainerRequest: req,
            Started:          true,
        })
        if err != nil {
            t.Fatal(err)
        }
    
        defer clickhouseContainer.Terminate(ctx)
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search