Pact Go
Golang version of Pact. Enables consumer driven contract testing, providing a mock service and
DSL for the consumer project, and interaction playback and verification for the service Provider project.
Implements Pact Specification v2,
including flexible matching.
From the Ruby Pact website:
Define a pact between service consumers and providers, enabling "consumer driven contract" testing.
Pact provides an RSpec DSL for service consumers to define the HTTP requests they will make to a service provider and the HTTP responses they expect back.
These expectations are used in the consumers specs to provide a mock service provider. The interactions are recorded, and played back in the service provider
specs to ensure the service provider actually does provide the response the consumer expects.
This allows testing of both sides of an integration point using fast unit tests.
This gem is inspired by the concept of "Consumer driven contracts". See http://martinfowler.com/articles/consumerDrivenContracts.html for more information.
Read Getting started with Pact for more information on
how to get going.
Installation
- Download a release for your OS.
- Unzip the package into a known location, and add to the
PATH
.
- Run
pact-go
to see what options are available.
Running
Due to some design constraints, Pact Go runs a two-step process:
- Run
pact-go daemon
in a separate process/shell. The Consumer and Provider
DSLs communicate over a local (RPC) connection, and is transparent to clients.
- Create your Pact Consumer/Provider Tests. It defaults to run on port
6666
.
NOTE: The daemon is completely thread safe and it is normal to leave the daemon
running for long periods (e.g. on a CI server).
Example - Consumer
- Start the daemon with
./pact-go daemon
.
cd <pact-go>/examples
.
go run consumer.go
.
import "github.com/pact-foundation/pact-go/dsl"
import ...
func TestSomeApi(t *testing.T) {
// Create Pact, connecting to local Daemon
// Ensure the port matches the daemon port!
pact := &dsl.Pact{
Port: 6666,
Consumer: "My Consumer",
Provider: "My Provider",
}
// Shuts down Mock Service when done
defer pact.Teardown()
// Pass in your test case as a function to Verify()
var test = func() error {
_, err := http.Get("http://localhost:8000/")
return err
}
// Set up our interactions. Note we have multiple in this test case!
pact.
AddInteraction().
Given("User Matt exists"). // Provider State
UponReceiving("A request to login"). // Test Case Name
WithRequest(&dsl.Request{
Method: "GET",
Path: "/login",
}).
WillRespondWith(&dsl.Response{
Status: 200,
})
// Run the test and verify the interactions.
err := pact.Verify(test)
if err != nil {
t.Fatalf("Error on Verify: %v", err)
}
// You should now have a pact file in the file `<pact-go>/pacts/my_consumer-my_provider.json`
}
Example - Provider
Start your Provider API:
mux := http.NewServeMux()
mux.HandleFunc("/setup", func(w http.ResponseWriter, req *http.Request) {
w.Header().Add("Content-Type", "application/json")
})
mux.HandleFunc("/states", func(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, `{"My Consumer": ["Some state", "Some state2"]}`)
w.Header().Add("Content-Type", "application/json")
})
mux.HandleFunc("/someapi", func(w http.ResponseWriter, req *http.Request) {
w.Header().Add("Content-Type", "application/json")
fmt.Fprintf(w, `
[
[
{
"size": 10,
"colour": "red",
"tag": [
[
"jumper",
"shirt"
],
[
"jumper",
"shirt"
]
]
}
]
]`)
})
go http.ListenAndServe(":8000"), mux)
Note that the server has 2 endpoints: /states
and /setup
that allows the
verifier to setup
provider states before
each test is run.
You can now tell Pact to read in your Pact files and verify that your API will
satisy the requirements of each of your known consumers:
response := pact.VerifyProvider(&types.VerifyRequest{
ProviderBaseURL: "http://localhost:8000",
PactURLs: []string{"./pacts/my_consumer-my_provider.json"},
ProviderStatesURL: "http://localhost:8000/states",
ProviderStatesSetupURL: "http://localhost:8000/setup",
})
See the Skip()'ed
integration tests
for a more complete E2E example.
Matching (Consumer Tests)
In addition to verbatim value matching, you have 3 useful matching functions
in the dsl
package that can increase expressiveness and reduce brittle test
cases.
dsl.Term(example, matcher)
- tells Pact that the value should match using
a given regular expression, using example
in mock responses. example
must be
a string.
dsl.Like(content)
- tells Pact that the value itself is not important, as long
as the element type (valid JSON number, string, object etc.) itself matches.
dsl.EachLike(content, min)
- tells Pact that the value should be an array type,
consisting of elements like those passed in. min
must be >= 1. content
may
be a valid JSON value: e.g. strings, numbers and objects.
Example:
Here is a complex example that shows how all 3 terms can be used together:
jumper := Like(`"jumper"`)
shirt := Like(`"shirt"`)
tag := EachLike(fmt.Sprintf(`[%s, %s]`, jumper, shirt), 2)
size := Like(10)
colour := Term("red", "red|green|blue")
match := formatJSON(
EachLike(
EachLike(
fmt.Sprintf(
`{
"size": %s,
"colour": %s,
"tag": %s
}`, size, colour, tag),
1),
1))
This example will result in a response body from the mock server that looks like:
[
[
{
"size": 10,
"colour": "red",
"tag": [
[
"jumper",
"shirt"
],
[
"jumper",
"shirt"
]
]
}
]
]
See the matcher tests
for more matching examples.
NOTE: One caveat to note, is that you will need to use valid Ruby
regular expressions and double
escape backslashes.
Read more about flexible matching.
Output Logging
Pact Go uses a simple log utility (logutils)
to filter log messages. The CLI already contains flags to manage this,
should you want to control log level in your tests, you can set it like so:
pact := &Pact{
...
LogLevel: "DEBUG", // One of DEBUG, INFO, ERROR, NONE
}
Documentation
Additional documentation can be found at the main Pact website and in the Pact Wiki.
Developing
For full integration testing locally, Ruby 2.1.5 must be installed. Under the
hood, Pact Go bundles the
Pact Mock Service and
Pact Provider Verifier
projects to implement up to v2.0 of the Pact Specification. This is only
temporary, until Pact Reference
work is completed.
Vendoring
We use Govend to vendor packages. Please ensure
any new packages are added to vendor.yml
prior to patching.
Roadmap
The roadmap for Pact and Pact Go is outlined on our main website.
Contributing
See CONTRIBUTING.