Skip to content

Commit 0b2272c

Browse files
committed
docs: split readme into multiple files
1 parent 7bd4e16 commit 0b2272c

21 files changed

+1329
-17
lines changed

README.md

+23-11
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,17 @@
3535

3636
**Why use Pact?**
3737

38-
Contract testing with Pact let's you:
38+
Contract testing with Pact lets you:
3939

4040
- ⚡ Test locally
4141
- 🚀 Deploy faster
4242
- ⬇️ Reduce the lead time for change
4343
- 💰 Reduce the cost of API integration testing
4444
- 💥 Prevent breaking changes
4545
- 🔎 Understand your system usage
46-
- 📃 Get documentation for free
47-
- 🗄 No need for complex data fixtures
48-
- 🤷‍♂️ No need for complex test environments
46+
- 📃 Document your APIs for free
47+
- 🗄 Remove the need for complex data fixtures
48+
- 🤷‍♂️ Reduce the reliance on complex test environments
4949

5050
</td>
5151
</tr>
@@ -55,12 +55,22 @@ Contract testing with Pact let's you:
5555

5656
## Documentation
5757

58-
This readme offers an basic introduction to the library. The full documentation for Pact Go and the rest of the framework is available at https://docs.pact.io/
58+
This readme offers an basic introduction to the library. The full documentation for Pact Go and the rest of the framework is available at https://docs.pact.io/.
5959

60-
- [Installation](https://docs.pact.io/LINK_HERE)
61-
- [Consumer Testing](https://docs.pact.io/LINK_HERE)
62-
- [Provider Testing](https://docs.pact.io/LINK_HERE)
63-
- [Event Driven Systems](https://docs.pact.io/LINK_HERE)
60+
- [Installation](#installation)
61+
- [Consumer Testing](./docs/consumer)
62+
- [Provider Testing](./docs/provider)
63+
- [Event Driven Systems](./docs/messages)
64+
- [Migration guide](./MIGRATION)
65+
- [Troubleshooting](./docs/troubleshooting)
66+
67+
### Why Pact?
68+
69+
Watch the [video series](https://www.youtube.com/playlist?list=PLwy9Bnco-IpfZ72VQ7hce8GicVZs7nm0i) on the problems with end-to-end integrated tests, and how contract testing can help.
70+
71+
### Tutorial (60 minutes)
72+
73+
Learn everything in Pact Go in 60 minutes: https://github.com/pact-foundation/pact-workshop-go
6474

6575
## Need Help
6676

@@ -80,6 +90,8 @@ pact-go -l DEBUG install
8090
# 🚀 now write some tests!
8191
```
8292

93+
You can also keep the library versions up to date by running the `version.CheckVersion()` function.
94+
8395
<details><summary>Manual Installation Instructions</summary>
8496

8597
### Manual
@@ -128,7 +140,7 @@ import (
128140

129141
### Consumer package
130142

131-
The consumer interface is in the package: `github.com/pact-foundation/pact-go/v2/consumer`
143+
The consumer interface is in the package: `github.com/pact-foundation/pact-go/v2/consumer`.
132144

133145
#### Writing a Consumer test
134146

@@ -218,7 +230,7 @@ func TestV3HTTPProvider(t *testing.T) {
218230

219231
| Version | Stable | [Spec] Compatibility | Install |
220232
| ------- | ------ | -------------------- | ------------------ |
221-
| 2.0.x | Yes | 2, 3 | See [installation] |
233+
| 2.0.x | Beta | 2, 3 | See [installation] |
222234
| 1.0.x | Yes | 2, 3\* | 1.x.x [1xx] |
223235
| 0.x.x | Yes | Up to v2 | 0.x.x [stable] |
224236

docs/consumer.md

+185
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
# Consumer Tests
2+
3+
## Contract Testing Process (HTTP)
4+
5+
Pact is a consumer-driven contract testing tool, which is a fancy way of saying that the API `Consumer` writes a test to set out its assumptions and needs of its API `Provider`(s). By unit testing our API client with Pact, it will produce a `contract` that we can share to our `Provider` to confirm these assumptions and prevent breaking changes.
6+
7+
The process looks like this:
8+
9+
![diagram](./diagrams/summary.png)
10+
11+
1. The consumer writes a unit test of its behaviour using a Mock provided by Pact
12+
1. Pact writes the interactions into a contract file (as a JSON document)
13+
1. The consumer publishes the contract to a broker (or shares the file in some other way)
14+
1. Pact retrieves the contracts and replays the requests against a locally running provider
15+
1. The provider should stub out its dependencies during a Pact test, to ensure tests are fast and more deterministic.
16+
17+
In this document, we will cover steps 1-3.
18+
19+
## Consumer package
20+
21+
The consumer interface is in the package: `github.com/pact-foundation/pact-go/v2/consumer`.
22+
23+
The two primary interfaces are `NewV2Pact` and `NewV3Pact`. If your provider is also V3 compatible, you can use the V3 variant, otherwise you should stick with V2.
24+
25+
## Writing a Consumer test
26+
27+
The purpose of a Pact test is to unit test the API Client of the consumer.
28+
29+
In this example, we are going to be testing our Product API client, responsible for communicating with the `ProductAPI` over HTTP. It currently has a single method `GetProduct(id)` that will return a `*Product`.
30+
31+
Pact tests have a few key properties. We'll demonstrate a common example using the 3A `Arrange/Act/Assert` pattern.
32+
33+
Here is a sequence diagram that shows how a consumer test works:
34+
35+
![diagram](./diagrams/workshop_step3_pact.svg)
36+
37+
### Example
38+
39+
```golang
40+
func TestProductAPIClient(t *testing.T) {
41+
// Specify the two applications in the integration we are testing
42+
// NOTE: this can usually be extracted out of the individual test for re-use)
43+
mockProvider, err := NewV2Pact(MockHTTPProviderConfig{
44+
Consumer: "ProductAPIConsumer",
45+
Provider: "ProductAPI",
46+
})
47+
assert.NoError(t, err)
48+
49+
// Arrange: Setup our expected interactions
50+
mockProvider.
51+
AddInteraction().
52+
Given("A Product with ID 10 exists").
53+
UponReceiving("A request for Product 10").
54+
WithRequest("GET", S("/product/10")).
55+
WillRespondWith(200).
56+
WithBodyMatch(&Product{})
57+
58+
// Act: test our API client behaves correctly
59+
err = mockProvider.ExecuteTest(func(config MockServerConfig) error {
60+
// Initialise the API client and point it at the Pact mock server
61+
// Pact spins up a dedicated mock server for each test
62+
client := newClient(config.Host, config.Port)
63+
64+
// Execute the API client
65+
product, err := client.GetProduct("10")
66+
67+
// Assert: check the result
68+
assert.NoError(t, err)
69+
assert.Equal(t, 10, product.ID)
70+
71+
return err
72+
})
73+
assert.NoError(t, err)
74+
}
75+
```
76+
77+
## Matching
78+
79+
In addition to matching on exact values, there are a number of useful matching functions
80+
in the `matching` package that can increase expressiveness of your tests and reduce brittle
81+
test cases.
82+
83+
Rather than use hard-coded values which must then be present on the Provider side,
84+
you can use regular expressions and type matches on objects and arrays to validate the
85+
structure of your APIs.
86+
87+
Matchers can be used on the `Body`, `Headers`, `Path` and `Query` fields of the request,
88+
and the `Body` and `Headers` on the response.
89+
90+
_NOTE: Some matchers are only compatible with the V3 interface, and must not be used with a V2 Pact. Your test will panic if this is attempted_
91+
92+
| Matcher | Min. Compatibility | Description |
93+
| ---------------------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
94+
| `Like(content)` | V2 | Tells Pact that the value itself is not important, as long as the element _type_ (valid JSON number, string, object etc.) itself matches. |
95+
| `Term(example, matcher)` | V2 | Tells Pact that the value should match using a given regular expression, using `example` in mock responses. `example` must be a string. |
96+
| `EachLike(content, min)` | V2 | Tells Pact that the value should be an array type, consisting of elements like those passed in. `min` must be >= 1. `content` may be any valid JSON value: e.g. strings, numbers and objects. |
97+
| `Equality(content)` | V3 | Matchers cascade, equality resets the matching process back to exact values |
98+
| `Includes(content)` | V3 | Checks if the given string is contained by the actual value |
99+
| `FromProviderState(expr, example)` | V3 | Marks an item as to be dynamically injected from the provider state during provider verification |
100+
| `EachKeyLike(key, template)` | V3 | Object where the key itself is ignored, but the value template must match. Useful for dynamic keys. |
101+
| `ArrayContaining(variants)` | V3 | Allows heterogenous items to be matched within a list. Unlike EachLike which must be an array with elements of the same shape, ArrayContaining allows objects of different types and shapes. Useful for hypermedia responses such as Siron, HAL and JSONAPI |
102+
| `ArrayMinMaxLike(min, max` | V3 | Like EachLike except has a bounds on the max and the min |
103+
| `ArrayMaxLike` | V3 | Like EachLike except has a bounds on the max |
104+
| `DateGenerated` | V3 | Matches a cross platform formatted date, and generates a current date during verification |
105+
| `TimeGenerated` | V3 | Matches a cross platform formatted date, and generates a current time during verification |
106+
| `DateTimeGenerated` | V3 | Matches a cross platform formatted datetime, and generates a current datetime during verification |
107+
108+
### Matching by regular expression
109+
110+
_Example:_
111+
112+
Here is a more complex example that shows how all 3 terms can be used together:
113+
114+
```go
115+
body :=
116+
Like(map[string]interface{}{
117+
"response": map[string]interface{}{
118+
"name": Like("Billy"),
119+
"type": Term("admin", "admin|user|guest"),
120+
"items": EachLike("cat", 2)
121+
},
122+
})
123+
```
124+
125+
This example will result in a response body from the mock server that looks like:
126+
127+
```json
128+
{
129+
"response": {
130+
"name": "Billy",
131+
"type": "admin",
132+
"items": ["cat", "cat"]
133+
}
134+
}
135+
```
136+
137+
### Match common formats
138+
139+
| method | Min. Compatibility | description |
140+
| --------------- | ------------------ | ----------------------------------------------------------------------------------------------- |
141+
| `Identifier()` | V2 | Match an ID (e.g. 42) |
142+
| `Integer()` | V3 | Match all numbers that are integers (both ints and longs) |
143+
| `Decimal()` | V3 | Match all real numbers (floating point and decimal) |
144+
| `HexValue()` | V2 | Match all hexadecimal encoded strings |
145+
| `Date()` | V2 | Match string containing basic ISO8601 dates (e.g. 2016-01-01) |
146+
| `Timestamp()` | V2 | Match a string containing an RFC3339 formatted timestamp (e.g. Mon, 31 Oct 2016 15:21:41 -0400) |
147+
| `Time()` | V2 | Match string containing times in ISO date format (e.g. T22:44:30.652Z) |
148+
| `IPv4Address()` | V2 | Match string containing IP4 formatted address |
149+
| `IPv6Address()` | V2 | Match string containing IP6 formatted address |
150+
| `UUID()` | V2 | Match strings containing UUIDs |
151+
152+
### Auto-generate matchers from struct tags
153+
154+
Furthermore, if you isolate your Data Transfer Objects (DTOs) to an adapters package so that they exactly reflect the interface between you and your provider, then you can leverage `WithBodyMatch(object)` option to auto-generate the expected response body in your contract tests. Under the hood, it recursively traverses the DTO struct and uses `Term, Like, and EachLike` to create the contract.
155+
156+
This saves the trouble of declaring the contract by hand. It also maintains one source of truth. To change the consumer-provider interface, you only have to update your DTO struct and the contract will automatically follow suit.
157+
158+
_Example:_
159+
160+
```go
161+
type DTO struct {
162+
ID string `json:"id"`
163+
Title string `json:"title"`
164+
Tags []string `json:"tags" pact:"min=2"`
165+
Date string `json:"date" pact:"example=2000-01-01,regex=^\\d{4}-\\d{2}-\\d{2}$"`
166+
}
167+
```
168+
169+
then specifying a response body is as simple as:
170+
171+
```go
172+
// Set up our expected interactions.
173+
pact.
174+
AddInteraction().
175+
...
176+
WithBodyMatch(DTO{}), // That's it!!!
177+
```
178+
179+
The `pact` struct tags shown above are optional. By default, it asserts that the JSON shape matches the struct and that the field types match.
180+
181+
## Publishing Pacts to a Broker
182+
183+
We recommend publishing the contracts to a Pact Broker (or https://pactflow.io) using the [CLI Tools]()https://docs.pact.io/implementation_guides/cli/#pact-cli.
184+
185+
[Read more](https://docs.pact.io/pact_broker/publishing_and_retrieving_pacts/) about publishing pacts.

docs/diagrams/message-consumer.png

189 KB
Loading

docs/diagrams/message-provider.png

250 KB
Loading

docs/diagrams/summary.png

395 KB
Loading

0 commit comments

Comments
 (0)