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.
CodePudding user response:
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)
CodePudding user response:
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.