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 theRuby 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.
PATH
. pact-go
to see what options are available. Due to some design constraints, Pact Go runs a two-step process:
pact-go daemon
in a separate process/shell. The Consumer and Provider DSLs communicate over a local (RPC) connection, and is transparent to clients. 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).
./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` }
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.
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 thematcher 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 aboutflexible matching.
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 }
Additional documentation can be found at the main Pact website and in thePact Wiki.
For full integration testing locally, Ruby 2.1.5 must be installed. Under the hood, Pact Go bundles thePact Mock Service and Pact Provider Verifier projects to implement up to v2.0 of the Pact Specification. This is only temporary, untilPact Reference work is completed.
make dev
to build the package and setup the Ruby 'binaries' locally We useGovend to vendor packages. Please ensure any new packages are added to vendor.yml
prior to patching.
Theroadmap for Pact and Pact Go is outlined on our main website.
SeeCONTRIBUTING.