Package testutils provides useful test helpers.
CaptureStdout: Captures stdout output from the provided functionCaptureStderr: Captures stderr output from the provided functionCaptureStdoutAndStderr: Captures both stdout and stderr from the provided function
These capture utilities are useful for testing functions that write directly to stdout/stderr. They redirect the standard outputs to a buffer and return the captured content as a string.
Important Note: The capture functions are not thread-safe if used in parallel tests. For concurrent tests, it's better to pass a custom io.Writer to the function under test instead.
WriteTestFile: Creates a temporary file with specified content and returns its path. The file is automatically cleaned up after the test completes.
MockHTTPServer: Creates a test HTTP server with the given handler. Returns the server URL and a cleanup function.HTTPRequestCaptor: Returns a request captor and an HTTP handler that captures and records HTTP requests for later inspection.
The containers package provides several test containers for integration testing:
SSHTestContainer: SSH server container for testing SSH connections with file operations (upload, download, list, delete)FTPTestContainer: FTP server container for testing FTP file operations (upload, download, list, delete)PostgresTestContainer: PostgreSQL database container with automatic database creationMySQLTestContainer: MySQL database container with automatic database creationMongoTestContainer: MongoDB container with support for multiple versions (5, 6, 7)LocalstackTestContainer: LocalStack container with S3 service for AWS testing, including file operations (upload, download, list, delete)
go get -u github.com/go-pkgz/testutils
// Capture stdoutfuncTestMyFunction(t*testing.T) {
output:=testutils.CaptureStdout(t, func() {
fmt.Println("Hello, World!")
})
assert.Equal(t, "Hello, World!\n", output)
}
// Capture stderrfuncTestErrorOutput(t*testing.T) {
errOutput:=testutils.CaptureStderr(t, func() {
fmt.Fprintln(os.Stderr, "Error message")
})
assert.Equal(t, "Error message\n", errOutput)
}
// Capture both stdout and stderrfuncTestBothOutputs(t*testing.T) {
stdout, stderr:=testutils.CaptureStdoutAndStderr(t, func() {
fmt.Println("Standard output")
fmt.Fprintln(os.Stderr, "Error output")
})
assert.Equal(t, "Standard output\n", stdout)
assert.Equal(t, "Error output\n", stderr)
}// Create a temporary test filefuncTestWithTempFile(t*testing.T) {
content:="test file content"filePath:=testutils.WriteTestFile(t, content)
// Use the file in your testdata, err:=os.ReadFile(filePath)
require.NoError(t, err)
assert.Equal(t, content, string(data))
// No need to clean up - it happens automatically when the test ends
}// Create a mock HTTP serverfuncTestWithMockServer(t*testing.T) {
handler:=http.HandlerFunc(func(w http.ResponseWriter, r*http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("response"))
})
serverURL, _:=testutils.MockHTTPServer(t, handler)
// Make requests to the serverresp, err:=http.Get(serverURL+"/path")
require.NoError(t, err)
deferresp.Body.Close()
assert.Equal(t, http.StatusOK, resp.StatusCode)
}
// Capture and inspect HTTP requestsfuncTestWithRequestCaptor(t*testing.T) {
// Create a request captorcaptor, handler:=testutils.HTTPRequestCaptor(t, nil)
// Create a server with the capturing handlerserverURL, _:=testutils.MockHTTPServer(t, handler)
// Make a requesthttp.Post(serverURL+"/api", "application/json", strings.NewReader(`{"key":"value"}`))
// Inspect the captured requestreq, _:=captor.GetRequest(0)
assert.Equal(t, http.MethodPost, req.Method)
assert.Equal(t, "/api", req.Path)
assert.Equal(t, `{"key":"value"}`, string(req.Body))
}// PostgreSQL test containerfuncTestWithPostgres(t*testing.T) {
ctx:=context.Background()
pg:=containers.NewPostgresTestContainer(ctx, t)
deferpg.Close(ctx)
db, err:=sql.Open("postgres", pg.ConnectionString())
require.NoError(t, err)
deferdb.Close()
// run your tests with the database
}
// MySQL test containerfuncTestWithMySQL(t*testing.T) {
ctx:=context.Background()
mysql:=containers.NewMySQLTestContainer(ctx, t)
defermysql.Close(ctx)
db, err:=sql.Open("mysql", mysql.DSN())
require.NoError(t, err)
deferdb.Close()
// run your tests with the database
}
// MongoDB test containerfuncTestWithMongo(t*testing.T) {
ctx:=context.Background()
mongo:=containers.NewMongoTestContainer(ctx, t, 7) // version 7defermongo.Close(ctx)
coll:=mongo.Collection("test_db")
_, err:=coll.InsertOne(ctx, bson.M{"test": "value"})
require.NoError(t, err)
}
// SSH test containerfuncTestWithSSH(t*testing.T) {
ctx:=context.Background()
ssh:=containers.NewSSHTestContainer(ctx, t)
deferssh.Close(ctx)
// use ssh.Address() to get host:port// default user is "test"sshAddr:=ssh.Address()
// Upload a file to the SSH serverlocalFile:="/path/to/local/file.txt"remotePath:="/config/file.txt"err:=ssh.SaveFile(ctx, localFile, remotePath)
require.NoError(t, err)
// Download a file from the SSH serverdownloadPath:="/path/to/download/location.txt"err=ssh.GetFile(ctx, remotePath, downloadPath)
require.NoError(t, err)
// List files on the SSH serverfiles, err:=ssh.ListFiles(ctx, "/config")
require.NoError(t, err)
for_, file:=rangefiles {
fmt.Println(file.Name(), file.Mode(), file.Size())
}
// Delete a file from the SSH servererr=ssh.DeleteFile(ctx, remotePath)
require.NoError(t, err)
}
// Localstack (S3) test containerfuncTestWithS3(t*testing.T) {
ctx:=context.Background()
ls:=containers.NewLocalstackTestContainer(ctx, t)
deferls.Close(ctx)
s3Client, bucketName:=ls.MakeS3Connection(ctx, t)
// put object example (using direct S3 API)_, err:=s3Client.PutObject(ctx, &s3.PutObjectInput{
Bucket: aws.String(bucketName),
Key: aws.String("test-key"),
Body: strings.NewReader("test content"),
})
require.NoError(t, err)
// File operations using higher-level container methods// Upload a file to S3localFile:="/path/to/local/file.txt"objectKey:="documents/file.txt"err=ls.SaveFile(ctx, localFile, bucketName, objectKey)
require.NoError(t, err)
// Download a file from S3downloadPath:="/path/to/download/location.txt"err=ls.GetFile(ctx, bucketName, objectKey, downloadPath)
require.NoError(t, err)
// List objects in bucket (optionally with prefix)objects, err:=ls.ListFiles(ctx, bucketName, "documents/")
require.NoError(t, err)
for_, obj:=rangeobjects {
fmt.Println(*obj.Key, *obj.Size)
}
// Delete an object from S3err=ls.DeleteFile(ctx, bucketName, objectKey)
require.NoError(t, err)
}
// FTP test containerfuncTestWithFTP(t*testing.T) {
ctx:=context.Background()
ftpContainer:=containers.NewFTPTestContainer(ctx, t)
deferftpContainer.Close(ctx)
// Connection detailsftpHost:=ftpContainer.GetIP() // Container hostftpPort:=ftpContainer.GetPort() // Container port (default: 2121)ftpUser:=ftpContainer.GetUser() // Default: "ftpuser"ftpPassword:=ftpContainer.GetPassword() // Default: "ftppass"// Upload a filelocalFile:="/path/to/local/file.txt"remotePath:="file.txt"err:=ftpContainer.SaveFile(ctx, localFile, remotePath)
require.NoError(t, err)
// Download a filedownloadPath:="/path/to/download/location.txt"err=ftpContainer.GetFile(ctx, remotePath, downloadPath)
require.NoError(t, err)
// List filesentries, err:=ftpContainer.ListFiles(ctx, "/")
require.NoError(t, err)
for_, entry:=rangeentries {
fmt.Println(entry.Name, entry.Type) // Type: 0 for file, 1 for directory
}
// Delete a fileerr=ftpContainer.DeleteFile(ctx, remotePath)
require.NoError(t, err)
}
// Using containers in TestMain for shared container across all tests// All containers have E-suffix variants that return errors instead of using require.NoErrorvarpgContainer*containers.PostgresTestContainerfuncTestMain(m*testing.M) {
ctx:=context.Background()
varerrerrorpgContainer, err=containers.NewPostgresTestContainerE(ctx)
iferr!=nil {
log.Fatalf("failed to start postgres container: %v", err)
}
code:=m.Run()
pgContainer.Close(ctx)
os.Exit(code)
}
funcTestWithSharedContainer(t*testing.T) {
// use pgContainer.ConnectionString() to connectdb, err:=sql.Open("postgres", pgContainer.ConnectionString())
require.NoError(t, err)
deferdb.Close()
// ...
}All container constructors have E-suffix variants that return (*Container, error) instead of using require.NoError. This is useful for TestMain where *testing.T is not available:
| Standard | Error-returning |
|---|---|
NewPostgresTestContainer | NewPostgresTestContainerE |
NewPostgresTestContainerWithDB | NewPostgresTestContainerWithDBE |
NewMySQLTestContainer | NewMySQLTestContainerE |
NewMySQLTestContainerWithDB | NewMySQLTestContainerWithDBE |
NewMongoTestContainer | NewMongoTestContainerE |
NewSSHTestContainer | NewSSHTestContainerE |
NewSSHTestContainerWithUser | NewSSHTestContainerWithUserE |
NewFTPTestContainer | NewFTPTestContainerE |
NewLocalstackTestContainer | NewLocalstackTestContainerE |