Throwaway Docker containers for MoonBit integration tests
Dependencies
///|
async test "postgres stores rows" {
let pg = @postgres.run()
defer pg.cleanup()
let dsn = pg.connection_string(ssl_mode="disable")
// ... connect to dsn and run queries ...
}moon add xziheng/testcontainers// your-project/moon.pkg
import {
"xziheng/testcontainers/postgres",
"xziheng/testcontainers/mysql",
"xziheng/testcontainers/s3mock",
} for "test"
import {
"moonbitlang/async",
} for "test"tests/
├── moon.pkg # import testcontainers for "test"; supported_targets = "native"
└── integration_test.mbt///|
async test "redis" {
let container = @testcontainers.ContainerRequest::new("redis:7-alpine")
.with_port(6379)
.with_wait_for(@testcontainers.WaitStrategy::TcpPort(6379))
.with_wait_timeout(60)
.start()
defer container.cleanup()
let port = container.host_port(6379)
let endpoint = container.endpoint(6379) // localhost:49155
}///|
async test "nginx" {
@testcontainers.with_container(
@testcontainers.ContainerRequest::new("nginx:1.27-alpine").with_port(80),
container => {
let url = "http://" + container.endpoint(80)
// ...
},
)
}///|
async test "postgres stores rows" {
let request = @testcontainers.ContainerRequest::new("postgres:16-alpine")
.with_port(5432)
.with_env("POSTGRES_PASSWORD", "secret")
.with_reuse()
// ...
}| Strategy | Ready when |
|---|---|
| NoWait | Immediately (the default) |
| TcpPort(port) | port listens inside the container |
| HttpGet(port, path, status) | GET path on the mapped port returns status |
| LogMessage(text) | The container logs contain text |
| Exec(cmd) | cmd exits 0 inside the container |
| Healthy | The image's HEALTHCHECK is healthy |
| Exit(code) | The container exited with code |
| Seconds(n) | n seconds passed |
| All(...) / Any(...) | All / at least one strategy holds |
container.host_port(5432) // mapped host port
container.endpoint(5432) // "localhost:49153"
container.logs() // stdout + stderr
container.exec(["psql", "-c", "SELECT 1"]) // code, stdout, stderr
container.copy_to("schema.sql", "/tmp/schema.sql")
container.copy_from("/tmp/dump.sql", "./dump.sql")
container.stop() // docker stop
container.remove() // docker rm --force --volumes
container.cleanup() // best effort, never raisespub(all) suberror TestContainerError {
DockerNotAvailable(String)
DockerCommandFailed(command~ : String, code~ : Int, stderr~ : String)
ImagePullFailed(image~ : String, stderr~ : String)
WaitTimeout(strategy~ : String, timeout_ms~ : Int, logs~ : String)
PortNotMapped(container_port~ : Int, container_id~ : String)
InvalidRequest(String)
ContainerNotRunning(container_id~ : String, status~ : String)
UnexpectedOutput(String)
IoFailed(String)
} derive(Debug)impl Show for TestContainerErrorpub(all) struct CommandOutput {
code : Int
stdout : String
stderr : String
}pub struct Container {
id : String
request : ContainerRequest
ports : Array[(Int, Int)]
host : String
reusable : Bool
}async test "nginx" {
@testcontainers.with_container(request, container => {
// ... use container ...
})
}async fn Container::copy_from(self : Container, container_path : String, host_path : String) -> Unit raise TestContainerErrorasync fn Container::copy_to(self : Container, host_path : String, container_path : String) -> Unit raise TestContainerErrorasync fn Container::exec(self : Container, cmd : ArrayView[String]) -> ExecOutput raise TestContainerErrorasync fn Container::exec_checked(self : Container, cmd : ArrayView[String]) -> String raise TestContainerErrorasync fn Container::logs(self : Container, options? : LogOptions) -> String raise TestContainerErrorasync fn Container::logs_split(self : Container, options? : LogOptions) -> LogOutput raise TestContainerErrorasync fn Container::wait_for(self : Container, strategy : WaitStrategy, timeout_ms? : Int) -> Unit raise TestContainerErrorpub(all) struct ContainerRequest {
image : String
ports : Array[Int]
env : Array[(String, String)]
cmd : Array[String]
entrypoint : Array[String]
mounts : Array[Mount]
labels : Array[(String, String)]
wait : WaitStrategy
wait_timeout_ms : Int
auto_remove : Bool
name : String?
user : String?
network : String?
hostname : String?
pull : PullPolicy
reuse : Bool
} derive(Eq, Debug)let request = @testcontainers.ContainerRequest::new("postgres:16-alpine")
.with_port(5432)
.with_env("POSTGRES_PASSWORD", "secret")
.with_wait_for(@testcontainers.WaitStrategy::TcpPort(5432))fn ContainerRequest::with_auto_remove(self : ContainerRequest, auto_remove : Bool) -> ContainerRequestfn ContainerRequest::with_bind(self : ContainerRequest, host_path : String, container_path : String) -> ContainerRequestfn ContainerRequest::with_bind_read_only(self : ContainerRequest, host_path : String, container_path : String) -> ContainerRequestfn ContainerRequest::with_entrypoint(self : ContainerRequest, entrypoint : ArrayView[String]) -> ContainerRequestfn ContainerRequest::with_env(self : ContainerRequest, key : String, value : String) -> ContainerRequestfn ContainerRequest::with_envs(self : ContainerRequest, env : ArrayView[(String, String)]) -> ContainerRequestfn ContainerRequest::with_label(self : ContainerRequest, key : String, value : String) -> ContainerRequestfn ContainerRequest::with_ports(self : ContainerRequest, ports : ArrayView[Int]) -> ContainerRequestfn ContainerRequest::with_pull_policy(self : ContainerRequest, pull : PullPolicy) -> ContainerRequestfn ContainerRequest::with_tmpfs(self : ContainerRequest, container_path : String) -> ContainerRequestfn ContainerRequest::with_volume(self : ContainerRequest, volume_name : String, container_path : String) -> ContainerRequestfn ContainerRequest::with_wait_for(self : ContainerRequest, wait : WaitStrategy) -> ContainerRequestfn ContainerRequest::with_wait_timeout(self : ContainerRequest, timeout_seconds : Int) -> ContainerRequestfn ContainerRequest::with_wait_timeout_ms(self : ContainerRequest, timeout_ms : Int) -> ContainerRequestpub(all) enum WaitStrategy {
NoWait
Seconds(Int)
TcpPort(Int)
HttpGet(Int, String, Int)
LogMessage(String)
Exec(Array[String])
Healthy
Exit(Int)
All(Array[WaitStrategy])
Any(Array[WaitStrategy])
} derive(Eq, Debug)fn join_host_port(host : String, port : Int) -> Stringfn short_id(id : String) -> Stringfn split_image_ref(image : String) -> (String, String)Install
Download zipThrowaway Docker containers for MoonBit integration tests
Dependencies