Writing Dredd Hooks In Go
Go hooks are using Dredd’s hooks handler socket interface. For using Go hooks in Dredd you have to have Dredd already installed. The Go library is called goodman
.
Installation
$ go get github.com/snikch/goodman/cmd/goodman
Usage
Using Dredd with Go is slightly different to other languages, as a binary needs to be compiled for execution. The --hookfiles
options should point to compiled hook binaries. See below for an example hooks.go file to get an idea of what the source file behind the go binary would look like.
$ dredd apiary.apib http://127.0.0.1:3000 --server=./go-lang-web-server-to-test --language=go --hookfiles=./hook-file-binary
Note
If you’re running Dredd inside Docker, read about specifics of getting it working together with non-JavaScript hooks.
API Reference
In order to get a general idea of how the Go Hooks work, the main executable from the package $GOPATH/bin/goodman is an HTTP Server that Dredd communicates with and an RPC client. Each hookfile then acts as a corresponding RPC server. So when Dredd notifies the Hooks server what transaction event is occuring the hooks server will execute all registered hooks on each of the hookfiles RPC servers.
You’ll need to know a few things about the Server
type in the hooks package.
The
hooks.Server
type is how you can define event callbacks such asbeforeEach
,afterAll
, etc.To get a
hooks.Server
struct you must do the following
package main
import (
"github.com/snikch/goodman/hooks"
trans "github.com/snikch/goodman/transaction"
)
func main() {
h := hooks.NewHooks()
server := hooks.NewServer(hooks.NewHooksRunner(h))
// Define all your event callbacks here
// server.Serve() will block and allow the goodman server to run your defined
// event callbacks
server.Serve()
// You must close the listener at end of main()
defer server.Listener.Close()
}
Callbacks receive a
Transaction
instance, or an array of themA
Server
will run yourRunner
and handle receiving events on the dredd socket.
Runner Callback Events
The Runner
type has the following callback methods.
BeforeEach
,BeforeEachValidation
,AfterEach
accepts a function as a first argument passing a Transaction object as a first argument
Before
,BeforeValidation
,After
accepts transaction name as a first argument
accepts a function as a second argument passing a Transaction object as a first argument of it
BeforeAll
,AfterAll
accepts a function as a first argument passing a Slice of Transaction objects as a first argument
Refer to Dredd execution lifecycle to find when each hook callback is executed.
Using the Go API
Example usage of all methods.
package main
import (
"fmt"
"github.com/snikch/goodman/hooks"
trans "github.com/snikch/goodman/transaction"
)
func main() {
h := hooks.NewHooks()
server := hooks.NewServer(hooks.NewHooksRunner(h))
h.BeforeAll(func(t []*trans.Transaction) {
fmt.Println("before all modification")
})
h.BeforeEach(func(t *trans.Transaction) {
fmt.Println("before each modification")
})
h.Before("/message > GET", func(t *trans.Transaction) {
fmt.Println("before modification")
})
h.BeforeEachValidation(func(t *trans.Transaction) {
fmt.Println("before each validation modification")
})
h.BeforeValidation("/message > GET", func(t *trans.Transaction) {
fmt.Println("before validation modification")
})
h.After("/message > GET", func(t *trans.Transaction) {
fmt.Println("after modification")
})
h.AfterEach(func(t *trans.Transaction) {
fmt.Println("after each modification")
})
h.AfterAll(func(t []*trans.Transaction) {
fmt.Println("after all modification")
})
server.Serve()
defer server.Listener.Close()
}
Examples
How to Skip Tests
Any test step can be skipped by setting the Skip
property of the Transaction
instance to true
.
package main
import (
"fmt"
"github.com/snikch/goodman/hooks"
trans "github.com/snikch/goodman/transaction"
)
func main() {
h := hooks.NewHooks()
server := hooks.NewServer(hooks.NewHooksRunner(h))
h.Before("Machines > Machines collection > Get Machines", func(t *trans.Transaction) {
t.Skip = true
})
server.Serve()
defer server.Listener.Close()
}
Failing Tests Programmatically
You can fail any step by setting the Fail
field of the Transaction
instance to true
or any string with a descriptive message.
package main
import (
"fmt"
"github.com/snikch/goodman/hooks"
trans "github.com/snikch/goodman/transaction"
)
func main() {
h := hooks.NewHooks()
server := hooks.NewServer(hooks.NewHooksRunner(h))
h.Before("Machines > Machines collection > Get Machines", func(t *trans.Transaction) {
t.Fail = true
})
h.Before("Machines > Machines collection > Post Machines", func(t *trans.Transaction) {
t.Fail = "POST is broken"
})
server.Serve()
defer server.Listener.Close()
}
Modifying the Request Body Prior to Execution
package main
import (
"fmt"
"github.com/snikch/goodman/hooks"
trans "github.com/snikch/goodman/transaction"
)
func main() {
h := hooks.NewHooks()
server := hooks.NewServer(hooks.NewHooksRunner(h))
h.Before("Machines > Machines collection > Get Machines", func(t *trans.Transaction) {
body := map[string]interface{}{}
json.Unmarshal([]byte(t.Request.Body), &body)
body["someKey"] = "new value"
newBody, _ := json.Marshal(body)
t.Request.Body = string(newBody)
})
server.Serve()
defer server.Listener.Close()
}