Conex integrates Go testing
with Docker so you can easily run your integration tests and benchmarks.
Yes, we did hear you like integrations.
Integration tests are very good value, they're easy to write and help you catch bugs in a more realistic environment and with most every service and database avaliable as a Docker Container, docker is a great option to run your service dependencies in a clear state. Conex is here to make it simpler by taking care of the following tasks:
- starting containers
- automatically creating uniqe names to avoid conflicts
- deleting containers
- pull or check images before running tests
- Wait for a service (tcp, udp) port to accept connections
- Expose ports
On top of that, Conex providers a driver convention to simplify code reuse across projects.
To use conex, we will leverage TestMain
, this will allow us a starting point to connect to docker, pull all the dependent images and only then run the tests.
Simpley call conex.Run(m)
where you would run m.Run()
.
func TestMain(m *testing.M) {
// If you're planing to use conex.Box directly without
// using a driver, you can pass your required images
// after m to conex.Run.
os.Exit(conex.Run(m))
}
In our tests, we will use driver
packages, these packages register their required image with conex and provide you with a native client and take cares of requesting a container from conex.
Here is an example using redis:
func testPing(t *testing.T) {
redisDb: = 0
client, container := redis.Box(t, redisDb)
defer container.Drop() // Return the container.
// here we can simply use client which is a go-redis
// client.
}
You can find find drivers/box packages for redis, mysql, postgresql, rethinkdb, and many more on github.com/conex.
Here is a complete example using a simple Echo service.
You can create many containers and different services as you want, you can also run multiple tests in parallel without conflict, conex creates the containers with uniqe names that consist of the test id, package path, test name, container, and an ordinal index starting from 0. This avoids container name conflicts across the board.
package example_test
import (
"os"
"testing"
"github.com/omeid/conex"
"github.com/omeid/conex/echo"
echolib "github.com/omeid/echo"
)
func TestMain(m *testing.M) {
os.Exit(conex.Run(m))
}
func TestEcho(t *testing.T) {
reverse := true
e, container := echo.Box(t, reverse)
defer container.Drop() // Return the container.
say := "hello"
expect := say
if reverse {
expect = echolib.Reverse(say)
}
reply, err := e.Say(say)
if err != nil {
t.Fatal(err)
}
if reply != expect {
t.Fatalf("\nSaid: %s\nExpected: %s\nGot: %s\n", say, expect, reply)
}
}
// You can also use containers in benchmarks!
func BenchmarkEcho(b *testing.B) {
reverse := false
say := "hello"
expect := say
e, c := echo.Box(b, reverse)
defer c.Drop()
for n := 0; n < b.N; n {
reply, err := e.Say(say)
if err != nil {
b.Fatal(err)
}
if reply != expect {
b.Fatalf("\nSaid: %s\nExpected: %s\nGot: %s\n", say, expect, reply)
}
}
}
And running tests will yield:
$ go test -v
2017/04/17 22:13:05
=== conex: Pulling Images
--- Pulling omeid/echo:http (1 of 1)
http: Pulling from omeid/echo
627beaf3eaaf: Already exists
8800e3417eb1: Already exists
b6acb96fee14: Already exists
66be5afddf19: Already exists
8ca17cdcfc93: Already exists
792cf0844f5e: Already exists
26601152322c: Pull complete
2cb3c6a6d3ee: Pull complete
Digest: sha256:f6968275ab031d91a3c37e8a9f65b961b5a3df850a90fe4551ecb4724ab3b0a7
Status: Downloaded newer image for omeid/echo:http
=== conex: Pulling Done
2017/04/17 22:13:38
2017/04/17 22:13:38
=== conex: Starting your tests.
=== RUN TestEcho
--- PASS: TestEcho (0.55s)
conex.go:11: creating (omeid/echo:http: -reverse) as conex_508151185_test-TestEcho-omeid_echo.http_0
conex.go:11: started (omeid/echo:http: -reverse) as conex_508151185_test-TestEcho-omeid_echo.http_0
PASS
ok test 33.753s
Conex drivers are simple packages that follow a convention to provide a simple interface to the underlying service run on the container. So the user doesn't have to think about containers but the service in their tests.
First, define an image attribute for your package that users can change and register it with conex.
// Image to use for the box.
var Image = "redis:alpine"
func init() {
conex.Require(func() string { return Image })
}
Then request a container with the required image from conex and setup a client that is connected to the container you created. Return the client and the container.
// Box returns an connect to an echo container based on
// your provided tags.
func Box(t testing.TB, optionally SomeOptions) (your.Client, conex.Container)) {
conf := &conex.Config{
Image: Image,
// Here you may set other options based
// on the options passed to Box.
}
c, con := conex.Box(t, conf)
opt := &your.Options{
Addr: c.Address(),
magic: optionally.SomeMagic,
}
client, err := redis.NewClient(opt)
if err != nil {
t.Fatal(err)
}
return client, con
}
Only native docker is support, so Docker for Mac and Docker Machine in general is not supported because of how port forwarding and port maping works.
Yes.
MIT.