pact 1.1.0.rc2 → 1.1.0.rc3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +0 -1
- data/CHANGELOG.md +46 -1
- data/Gemfile.lock +6 -4
- data/README.md +40 -186
- data/Rakefile +1 -1
- data/documentation/README.md +10 -0
- data/documentation/best-practices.md +33 -0
- data/documentation/configuration.md +166 -0
- data/documentation/diff_formatter_embedded.png +0 -0
- data/documentation/diff_formatter_list.png +0 -0
- data/documentation/diff_formatter_unix.png +0 -0
- data/documentation/faq.md +36 -6
- data/documentation/provider-states.md +173 -0
- data/documentation/raq.md +4 -4
- data/documentation/terminology.md +2 -2
- data/example/animal-service/Gemfile.lock +6 -9
- data/example/animal-service/Rakefile +2 -0
- data/example/animal-service/db/animal_db.sqlite3 +0 -0
- data/example/animal-service/lib/animal_service/animal_repository.rb +1 -5
- data/example/animal-service/lib/animal_service/api.rb +1 -1
- data/example/animal-service/spec/service_consumers/pact_helper.rb +7 -10
- data/example/animal-service/spec/service_consumers/provider_states_for_zoo_app.rb +5 -1
- data/example/zoo-app/Gemfile.lock +6 -9
- data/example/zoo-app/Rakefile +5 -0
- data/example/zoo-app/doc/markdown/README.md +3 -0
- data/example/zoo-app/doc/markdown/Zoo App - Animal Service.md +75 -0
- data/example/zoo-app/lib/zoo_app/animal_service_client.rb +2 -2
- data/example/zoo-app/spec/pacts/zoo_app-animal_service.json +1 -1
- data/example/zoo-app/spec/service_providers/animal_service_client_spec.rb +29 -34
- data/example/zoo-app/spec/service_providers/pact_helper.rb +4 -0
- data/lib/pact/configuration.rb +49 -1
- data/lib/pact/consumer/configuration.rb +4 -172
- data/lib/pact/consumer/configuration/configuration_extensions.rb +15 -0
- data/lib/pact/consumer/configuration/dsl.rb +12 -0
- data/lib/pact/consumer/configuration/mock_service.rb +89 -0
- data/lib/pact/consumer/configuration/service_consumer.rb +51 -0
- data/lib/pact/consumer/configuration/service_provider.rb +40 -0
- data/lib/pact/consumer/mock_service/interaction_mismatch.rb +3 -3
- data/lib/pact/consumer/mock_service/interaction_post.rb +2 -2
- data/lib/pact/consumer/mock_service/interaction_replay.rb +3 -4
- data/lib/pact/consumer/mock_service/verification_get.rb +32 -13
- data/lib/pact/consumer/rspec.rb +2 -4
- data/lib/pact/consumer/spec_hooks.rb +3 -1
- data/lib/pact/consumer_contract/consumer_contract.rb +1 -1
- data/lib/pact/consumer_contract/interaction.rb +1 -1
- data/lib/pact/doc/doc_file.rb +40 -0
- data/lib/pact/doc/generate.rb +11 -0
- data/lib/pact/doc/generator.rb +81 -0
- data/lib/pact/doc/interaction_view_model.rb +113 -0
- data/lib/pact/doc/markdown/generator.rb +26 -0
- data/lib/pact/doc/markdown/index_renderer.rb +41 -0
- data/lib/pact/doc/markdown/interaction.erb +14 -0
- data/lib/pact/doc/markdown/interaction_renderer.rb +38 -0
- data/lib/pact/doc/markdown/interactions_renderer.rb +56 -0
- data/lib/pact/doc/sort_interactions.rb +17 -0
- data/lib/pact/matchers/actual_type.rb +16 -0
- data/lib/pact/matchers/base_difference.rb +37 -0
- data/lib/pact/matchers/differ.rb +150 -0
- data/lib/pact/matchers/difference.rb +5 -30
- data/lib/pact/matchers/difference_indicator.rb +26 -0
- data/lib/pact/matchers/embedded_diff_formatter.rb +62 -0
- data/lib/pact/matchers/expected_type.rb +35 -0
- data/lib/pact/matchers/index_not_found.rb +3 -12
- data/lib/pact/matchers/{diff_decorator.rb → list_diff_formatter.rb} +28 -11
- data/lib/pact/matchers/matchers.rb +35 -39
- data/lib/pact/matchers/no_diff_indicator.rb +18 -0
- data/lib/pact/matchers/regexp_difference.rb +13 -0
- data/lib/pact/matchers/type_difference.rb +16 -0
- data/lib/pact/matchers/unexpected_index.rb +3 -13
- data/lib/pact/matchers/unexpected_key.rb +3 -12
- data/lib/pact/matchers/{plus_minus_diff_decorator.rb → unix_diff_formatter.rb} +22 -7
- data/lib/pact/provider/configuration.rb +5 -178
- data/lib/pact/provider/configuration/configuration_extension.rb +58 -0
- data/lib/pact/provider/configuration/dsl.rb +13 -0
- data/lib/pact/provider/configuration/pact_verification.rb +46 -0
- data/lib/pact/provider/configuration/service_provider_config.rb +16 -0
- data/lib/pact/provider/configuration/service_provider_dsl.rb +54 -0
- data/lib/pact/provider/matchers.rb +21 -13
- data/lib/pact/provider/matchers/messages.rb +43 -0
- data/lib/pact/provider/pact_spec_runner.rb +8 -0
- data/lib/pact/provider/rspec.rb +1 -1
- data/lib/pact/provider/rspec/formatter.rb +9 -7
- data/lib/pact/{consumer_contract → shared}/active_support_support.rb +4 -0
- data/lib/pact/shared/jruby_support.rb +18 -0
- data/lib/pact/shared/key_not_found.rb +3 -16
- data/lib/pact/shared/request.rb +5 -5
- data/lib/pact/term.rb +2 -2
- data/lib/pact/version.rb +1 -1
- data/pact.gemspec +2 -2
- data/spec/integration/pact/provider_configuration_spec.rb +2 -1
- data/spec/lib/pact/configuration_spec.rb +73 -0
- data/spec/lib/pact/consumer/mock_service/interaction_mismatch_spec.rb +21 -3
- data/spec/lib/pact/consumer/mock_service/verification_get_spec.rb +134 -0
- data/spec/lib/pact/consumer/request_spec.rb +1 -1
- data/spec/lib/pact/consumer_contract/active_support_support_spec.rb +1 -1
- data/spec/lib/pact/doc/generator_spec.rb +69 -0
- data/spec/lib/pact/doc/interaction_view_model_spec.rb +112 -0
- data/spec/lib/pact/doc/markdown/index_renderer_spec.rb +29 -0
- data/spec/lib/pact/doc/markdown/interactions_renderer_spec.rb +29 -0
- data/spec/lib/pact/matchers/differ_spec.rb +214 -0
- data/spec/lib/pact/matchers/difference_spec.rb +2 -12
- data/spec/lib/pact/matchers/embedded_diff_formatter_spec.rb +77 -0
- data/spec/lib/pact/matchers/index_not_found_spec.rb +21 -0
- data/spec/lib/pact/matchers/list_diff_formatter_spec.rb +114 -0
- data/spec/lib/pact/matchers/matchers_spec.rb +38 -22
- data/spec/lib/pact/matchers/regexp_difference_spec.rb +20 -0
- data/spec/lib/pact/matchers/type_difference_spec.rb +34 -0
- data/spec/lib/pact/matchers/unexpected_index_spec.rb +20 -0
- data/spec/lib/pact/matchers/unexpected_key_spec.rb +20 -0
- data/spec/lib/pact/matchers/{plus_minus_diff_decorator_spec.rb → unix_diff_formatter_spec.rb} +35 -6
- data/spec/lib/pact/provider/configuration/configuration_extension_spec.rb +30 -0
- data/spec/lib/pact/provider/configuration/pact_verification_spec.rb +43 -0
- data/spec/lib/pact/provider/configuration/service_provider_config_spec.rb +21 -0
- data/spec/lib/pact/provider/configuration/service_provider_dsl_spec.rb +92 -0
- data/spec/lib/pact/provider/configuration_spec.rb +7 -150
- data/spec/lib/pact/provider/matchers/messages_spec.rb +104 -0
- data/spec/lib/pact/provider/rspec/formatter_spec.rb +56 -0
- data/spec/lib/pact/shared/key_not_found_spec.rb +20 -0
- data/spec/lib/pact/shared/request_spec.rb +28 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/standalone/consumer_fail_test.rb +54 -0
- data/spec/standalone/consumer_pass_test.rb +50 -0
- data/spec/support/generated_index.md +4 -0
- data/spec/support/generated_markdown.md +55 -0
- data/spec/support/interaction_view_model.json +63 -0
- data/spec/support/markdown_pact.json +48 -0
- data/spec/support/pact_helper.rb +2 -1
- data/spec/support/spec_support.rb +7 -0
- data/spec/support/test_app_fail.json +11 -2
- data/tasks/pact-test.rake +9 -0
- metadata +113 -20
- data/example/animal-service/db/animals_db.sqlite3 +0 -0
- data/lib/pact/consumer/rspec/full_example_description.rb +0 -28
- data/lib/pact/matchers/nested_json_diff_decorator.rb +0 -53
- data/spec/lib/pact/matchers/diff_decorator_spec.rb +0 -80
- data/spec/lib/pact/matchers/nested_json_diff_decorator_spec.rb +0 -48
data/Rakefile
CHANGED
@@ -5,5 +5,5 @@ Dir.glob('lib/tasks/**/*.rake').each { |task| load task }
|
|
5
5
|
Dir.glob('tasks/**/*.rake').each { |task| load task }
|
6
6
|
RSpec::Core::RakeTask.new(:spec)
|
7
7
|
|
8
|
-
task :default => [:spec, :spec_with_active_support, 'pact:tests']
|
8
|
+
task :default => [:spec, :spec_with_active_support, 'spec:standalone:pass', 'pact:tests']
|
9
9
|
|
@@ -0,0 +1,10 @@
|
|
1
|
+
### Pact Documentation
|
2
|
+
|
3
|
+
* Step by step instructions for getting started with pacts can be found in the project [README.md](/README.md#usage)
|
4
|
+
* [Terminology](terminology.md)
|
5
|
+
* [Configuration](configuration.md)
|
6
|
+
* [Provider States](provider-states.md)
|
7
|
+
* [Frequently asked questions](faq.md)
|
8
|
+
* [Rarely asked questions](raq.md)
|
9
|
+
* [Best practices](best-practices.md)
|
10
|
+
* [Testing with pact diagram](Testing with pact.png)
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# Pact best practices
|
2
|
+
|
3
|
+
## In your consumer project
|
4
|
+
|
5
|
+
#### Publish your pacts as artifacts on your CI machine or use a [Pact Broker](https://github.com/bethesque/pact_broker)
|
6
|
+
|
7
|
+
Either of these techniques makes the pact available via URL, which your provider build can then use when it runs pact:verify. This means your provider will always be verified against the latest pact from your consumer.
|
8
|
+
|
9
|
+
#### Ensure all calls to the provider go through your provider client class
|
10
|
+
|
11
|
+
Do not hand create any HTTP requests in your consumer app or specs. Testing through your provider client class gives you the assurance that your consumer app will be creating exactly the HTTP requests that you think it should.
|
12
|
+
|
13
|
+
#### Use factories to create your expected models
|
14
|
+
|
15
|
+
Sure, you've checked that your client deserialises the HTTP response into the object you expect, but then you need to make sure in your other tests where you stub your client that you're stubbing it with a valid object. The best way to do this is to use factories for all your tests.
|
16
|
+
|
17
|
+
## In your provider project
|
18
|
+
|
19
|
+
#### Retrieve pacts from the [Pact Broker](https://github.com/bethesque/pact_broker) or use the pact artifact published by your consumer's CI build
|
20
|
+
|
21
|
+
Configure the pact_uri in the Pact.service_provider block with the pact URL of your last successful build, whether that's from the pact broker, or your CI build. This way you're only verifying green builds. No point verifying a broken one.
|
22
|
+
|
23
|
+
#### Add pact:verify to your default rake task
|
24
|
+
|
25
|
+
It should run with all your other tests. If an integration is broken, you want to know about it *before* you check in.
|
26
|
+
|
27
|
+
#### In pact:verify on the provider, only stub layers beneath where contents of the request body are extracted
|
28
|
+
|
29
|
+
If you don't _have_ to stub anything in the provider when running pact:verify, then don't. If you do need to stub something, make sure that you only stub the code that gets executed _after_ the contents of the request body have been extracted and/or validated, otherwise, there is no verification that what is included in the body of a request matches what is actually expected.
|
30
|
+
|
31
|
+
#### Stub calls to downstream systems
|
32
|
+
|
33
|
+
Consider making a separate pact with the downstream system and using shared fixtures.
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# Configuration
|
2
|
+
|
3
|
+
## Menu
|
4
|
+
|
5
|
+
#### Consumer and Provider configuration options
|
6
|
+
* [diff_formatter](#diff_formatter)
|
7
|
+
* [log_dir](#log_dir)
|
8
|
+
* [logger](#logger)
|
9
|
+
* [logger.level](#loggerlevel)
|
10
|
+
|
11
|
+
#### Consumer only configuration options
|
12
|
+
* [pact_dir](#pact_dir)
|
13
|
+
* [doc_dir](#doc_dir)
|
14
|
+
* [doc_generator](#doc_generator)
|
15
|
+
* [pactfile_write_mode](#pactfile_write_mode)
|
16
|
+
|
17
|
+
#### Provider only configuration options
|
18
|
+
* [include](#include)
|
19
|
+
|
20
|
+
## Consumer and Provider
|
21
|
+
|
22
|
+
### log_dir
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
Pact.configure do | config |
|
26
|
+
config.log_dir = './log'
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
Default value: `./log`
|
31
|
+
|
32
|
+
### logger
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
Pact.configure do | config |
|
36
|
+
config.logger = Logger.new
|
37
|
+
end
|
38
|
+
```
|
39
|
+
|
40
|
+
Default value: file logger to the configured log_dir.
|
41
|
+
|
42
|
+
### logger.level
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
Pact.configure do | config |
|
46
|
+
config.logger.level = Logger::INFO
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
Default value: `Logger::DEBUG`
|
51
|
+
|
52
|
+
### diff_formatter
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
Pact.configure do | config |
|
56
|
+
config.diff_formatter = :list
|
57
|
+
end
|
58
|
+
```
|
59
|
+
|
60
|
+
Default value: [:list](#list)
|
61
|
+
|
62
|
+
Options: [:unix](#unix), [:list](#list), [:embedded](#embedded), [Custom Diff Formatter](#custom-diff-formatter)
|
63
|
+
|
64
|
+
|
65
|
+
#### :unix
|
66
|
+
<img src="diff_formatter_unix.png" width="700">
|
67
|
+
|
68
|
+
#### :list
|
69
|
+
|
70
|
+
<img src="diff_formatter_list.png" width="700">
|
71
|
+
|
72
|
+
#### :embedded
|
73
|
+
|
74
|
+
<img src="diff_formatter_embedded.png" width="700">
|
75
|
+
|
76
|
+
|
77
|
+
#### Custom Diff Formatter
|
78
|
+
|
79
|
+
Any object can be used that responds to `call`, accepting the argument `diff`.
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
class MyCustomDiffFormatter
|
83
|
+
|
84
|
+
def self.call diff
|
85
|
+
### Do stuff here
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
Pact.configure do | config |
|
91
|
+
config.diff_formatter = MyCustomDiffFormatter
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
|
96
|
+
## Consumer
|
97
|
+
|
98
|
+
### pact_dir
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
Pact.configure do | config |
|
102
|
+
config.pact_dir = `./spec/pacts`
|
103
|
+
end
|
104
|
+
```
|
105
|
+
|
106
|
+
Default value: `./spec/pacts`
|
107
|
+
|
108
|
+
### doc_generator
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
Pact.configure do | config |
|
112
|
+
config.doc_generator = :markdown
|
113
|
+
end
|
114
|
+
```
|
115
|
+
|
116
|
+
Default value: none
|
117
|
+
|
118
|
+
Options: [:markdown](#markdown), [Custom Doc Generator](#custom-doc-generator)
|
119
|
+
|
120
|
+
#### :markdown
|
121
|
+
|
122
|
+
Generates Markdown documentation based on the contents of the pact files created in this consumer. Files are created in `${Pact.configuration.doc_dir}/markdown`.
|
123
|
+
|
124
|
+
#### Custom Doc Generator
|
125
|
+
|
126
|
+
Any object can be used that responds to `call`, accepting the arguments `pact_dir` and `doc_dir`.
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
Pact.configure do | config |
|
130
|
+
config.doc_generator = lambda{ | pact_dir, doc_dir | generate_some_docs(pact_dir, doc_dir) }
|
131
|
+
end
|
132
|
+
|
133
|
+
```
|
134
|
+
|
135
|
+
#### doc_dir
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
Pact.configure do | config |
|
139
|
+
config.doc_dir = './doc'
|
140
|
+
end
|
141
|
+
```
|
142
|
+
|
143
|
+
Default value: `./doc`
|
144
|
+
|
145
|
+
|
146
|
+
### pactfile_write_mode
|
147
|
+
|
148
|
+
Default value: `:overwrite`
|
149
|
+
Options: `:overwrite`, `:update`, `:smart`
|
150
|
+
|
151
|
+
By default, the pact file will be overwritten (started from scratch) every time any rspec runs any spec using pacts. This means that if there are interactions that haven't been executed in the most recent rspec run, they are effectively removed from the pact file. If you have long running pact specs (e.g. they are generated using the browser with Capybara) and you are developing both consumer and provider in parallel, or trying to fix a broken interaction, it can be tedious to run all the specs at once. In this scenario, you can set the pactfile_write_mode to :update. This will keep all existing interactions, and update only the changed ones, identified by description and provider state. The down side of this is that if either of those fields change, the old interactions will not be removed from the pact file. As a middle path, you can set pactfile_write_mode to :smart. This will use :overwrite mode when running rake (as determined by a call to system using 'ps') and :update when running an individual spec.
|
152
|
+
|
153
|
+
## Provider
|
154
|
+
|
155
|
+
Pact uses RSpec and Rack::Test to create dynamic specs based on the pact files. RSpec configuration can be used to modify test behaviour if there is not an appropriate Pact feature. If you wish to use the same spec_helper.rb file as your unit tests, require it in the pact_helper.rb, but remember that the RSpec configurations for your unit tests may or may not be what you want for your pact verification tests.
|
156
|
+
|
157
|
+
### include
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
Pact.configure do | config |
|
161
|
+
config.include RSpec::Mocks::ExampleMethods
|
162
|
+
end
|
163
|
+
```
|
164
|
+
|
165
|
+
To make modules available in the provider state set_up and tear_down blocks, include them in the configuration as shown below. One common use of this is to include RSpec::Mocks::ExampleMethods to make the `allow()` method available.
|
166
|
+
|
Binary file
|
Binary file
|
Binary file
|
data/documentation/faq.md
CHANGED
@@ -1,6 +1,24 @@
|
|
1
1
|
# Frequently asked questions
|
2
2
|
|
3
|
-
|
3
|
+
### How does Pact differ from VCR?
|
4
|
+
|
5
|
+
Pact is like VCR in reverse. VCR records actual provider behaviour, and verifies that the consumer behaves as expected. Pact records consumer behaviour, and verifies that the provider behaves as expected. The advantages Pact provides are:
|
6
|
+
|
7
|
+
* The ability to develop the consumer (eg. a Javascript rich client UI) before the provider (eg. the JSON backend API).
|
8
|
+
* The ability to drive out the requirements for your provider first, meaning you implement exactly and only what you need in the provider.
|
9
|
+
* Well documented use cases ("Given ... a request for ... will return ...") that show exactly how a provider is being used.
|
10
|
+
* The ability to see exactly which fields each consumer is interested in, allowing unused fields to be removed, and new fields to be added in the provider API without impacting a consumer.
|
11
|
+
* The ability to immediately see which consumers will be broken if a change is made to the provider API.
|
12
|
+
* When using the [Pact Broker](https://github.com/bethesque/pact_broker), the ability to map the relationships between your services.
|
13
|
+
|
14
|
+
### How does Pact differ from Webmock?
|
15
|
+
|
16
|
+
Unlike Webmock:
|
17
|
+
|
18
|
+
* Pact provides verification that the responses that have been stubbed are actually the responses that will be returned in the given conditions.
|
19
|
+
* Pact runs a mock server in an actual process, rather than intercepting requests within the Ruby code, allowing Javascript rich UI clients to be tested in a browser.
|
20
|
+
|
21
|
+
### How can I verify a pact against a non-ruby provider?
|
4
22
|
|
5
23
|
You can verify a pact against any running server, regardless of language, using [pact-provider-proxy](https://github.com/bethesque/pact-provider-proxy).
|
6
24
|
|
@@ -12,13 +30,20 @@ Become famous, and write a pact-consumer library yourself! Then let us know abou
|
|
12
30
|
|
13
31
|
### How can I specify hooks to be executed before/after all examples for pact:verify?
|
14
32
|
|
15
|
-
|
33
|
+
Use the set_up and tear_down hooks in the provider state definition:
|
16
34
|
|
17
35
|
```ruby
|
18
|
-
|
19
|
-
|
20
|
-
|
36
|
+
|
37
|
+
Pact.provider_states_for "Some Consumer" do
|
38
|
+
|
39
|
+
set_up do
|
40
|
+
# Set up code here
|
41
|
+
end
|
42
|
+
|
43
|
+
tear_down do
|
44
|
+
# tear down code here
|
21
45
|
end
|
46
|
+
|
22
47
|
end
|
23
48
|
```
|
24
49
|
|
@@ -41,5 +66,10 @@ This is a hotly debated issue.
|
|
41
66
|
|
42
67
|
The pact authors' experience with using pacts to test microservices has been that using the set_up hooks to populate the database, and running pact:verify with all the real provider code has worked very well, and gives us full confidence that the end to end scenario will work in the deployed code.
|
43
68
|
|
44
|
-
However, if you have a large and complex provider, you might decide to stub some of your application code.
|
69
|
+
However, if you have a large and complex provider, you might decide to stub some of your application code. You will definitly need to stub calls to downstream systems. Make sure, when you stub, that you don't stub the code that actually parses the requests, because otherwise the consumer could be sending absolute rubbish, and you won't be checking it.
|
70
|
+
|
71
|
+
### Why are the pacts generated and not hand coded?
|
72
|
+
|
73
|
+
* Maintainability: Pact is "contract by example", and the examples may involve large quantities of JSON. Maintaining the JSON files by hand would be both time consuming and error prone. By dynamically creating the pacts, you have the option to keep your expectations in fixture files, or to generate them from your domain (the recommended approach, as it ensures your domain objects and their JSON representations in the pacts can never get out of sync).
|
45
74
|
|
75
|
+
* Provider states: Dynamically setting expectations on the mock server allows the use of provider states, meaning you can make the same request more than once, with different expected responses, allowing you to properly test all your code paths.
|
@@ -0,0 +1,173 @@
|
|
1
|
+
# Provider States
|
2
|
+
|
3
|
+
Provider states allow you to set up data on the provider before the interaction is run, so that it can make a response that matches what the consumer expects. It also allows the consumer to make the same request with different expected responses.
|
4
|
+
|
5
|
+
### Consumer codebase
|
6
|
+
|
7
|
+
For example, some code that creates a pact in a consumer project might look like this:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
describe MyServiceProviderClient do
|
11
|
+
|
12
|
+
subject { MyServiceProviderClient.new }
|
13
|
+
|
14
|
+
describe "get_something" do
|
15
|
+
context "when a thing exists" do
|
16
|
+
before do
|
17
|
+
my_service.given("a thing exists").
|
18
|
+
upon_receiving("a request for a thing").with(method: 'get', path: '/thing').
|
19
|
+
will_respond_with(status: 200,
|
20
|
+
headers: { 'Content-Type' => 'application/json' },
|
21
|
+
body: { name: 'A small something'} )
|
22
|
+
end
|
23
|
+
|
24
|
+
it "returns a thing" do
|
25
|
+
expect(subject.get_something).to eq(SomethingModel.new('A small something'))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "when a thing does not exist" do
|
30
|
+
before do
|
31
|
+
my_service.given("a thing does not exist").
|
32
|
+
upon_receiving("a request for a thing").with(method: 'get', path: '/thing').
|
33
|
+
will_respond_with(status: 404)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "returns nil" do
|
37
|
+
expect(subject.get_something).to be_nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
### Provider codebase
|
45
|
+
|
46
|
+
To define service provider states that create the right data for the provider states described above, write the following in the service provider project. (The consumer name here must match the name of the consumer configured in your consumer project for it to correctly find these provider states.)
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
# In /spec/service_consumers/provider_states_for_my_service_consumer.rb
|
50
|
+
|
51
|
+
Pact.provider_states_for 'My Service Consumer' do
|
52
|
+
|
53
|
+
provider_state "a thing exists" do
|
54
|
+
set_up do
|
55
|
+
# Create a thing here using your framework of choice
|
56
|
+
# eg. Sequel.sqlite[:somethings].insert(name: "A small something")
|
57
|
+
end
|
58
|
+
|
59
|
+
tear_down do
|
60
|
+
# Any tear down steps to clean up your code
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
provider_state "a thing does not exist" do
|
65
|
+
no_op # If there's nothing to do because the state name is more for documentation purposes,
|
66
|
+
# you can use no_op to imply this.
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
```
|
71
|
+
Require your provider states file in the `pact_helper.rb`
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
# In /spec/service_consumers/pact_helper.rb
|
75
|
+
|
76
|
+
require './spec/service_consumers/provider_states_for_my_service_consumer.rb'
|
77
|
+
```
|
78
|
+
|
79
|
+
### Base state
|
80
|
+
|
81
|
+
To define code that should run before/after each interaction for a given consumer, regardless of whether a provider state is specified or not, define set_up/tear_down blocks with no wrapping provider_state.
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
Pact.provider_states_for 'My Service Consumer' do
|
85
|
+
|
86
|
+
set_up do
|
87
|
+
# This will run before the set_up for provider state specified for the interaction.
|
88
|
+
# eg. create API user, set the expected basic auth details
|
89
|
+
end
|
90
|
+
|
91
|
+
tear_down do
|
92
|
+
# ...
|
93
|
+
# This will run after the tear_down for the specified provider state.
|
94
|
+
end
|
95
|
+
end
|
96
|
+
```
|
97
|
+
|
98
|
+
### Global state
|
99
|
+
|
100
|
+
Global state will be set up before consumer specific base state. Avoid using the global set up for creating data as it will make your tests brittle when more than one consumer exists.
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
Pact.set_up do
|
104
|
+
# eg. start database cleaner transaction
|
105
|
+
end
|
106
|
+
|
107
|
+
Pact.tear_down do
|
108
|
+
# eg. clean database
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
### Testing error responses
|
113
|
+
|
114
|
+
It is important to test how your client will handle error responses.
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
# Consumer codebase
|
118
|
+
|
119
|
+
describe MyServiceProviderClient do
|
120
|
+
|
121
|
+
subject { MyServiceProviderClient.new }
|
122
|
+
|
123
|
+
describe "get_something" do
|
124
|
+
|
125
|
+
context "when an error occurs retrieving a thing" do
|
126
|
+
before do
|
127
|
+
my_service.given("an error occurs while retrieving a thing").
|
128
|
+
upon_receiving("a request for a thing").with(method: 'get', path: '/thing').
|
129
|
+
will_respond_with(
|
130
|
+
status: 500,
|
131
|
+
headers: { 'Content-Type' => 'application/json' },
|
132
|
+
body: { message: "An error occurred!" } )
|
133
|
+
end
|
134
|
+
|
135
|
+
it "raises an error" do
|
136
|
+
expect{ subject.get_something }.to raise_error /An error occurred!/
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
```
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
# Provider codebase
|
146
|
+
|
147
|
+
Pact.provider_states_for 'My Service Consumer' do
|
148
|
+
provider_state "an error occurs while retrieving a thing" do
|
149
|
+
set_up do
|
150
|
+
# Stubbing is ususally the easiest way to generate an error with predictable error text.
|
151
|
+
ThingRepository.stub(:find).and_raise("An error occurred!")
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
```
|
156
|
+
|
157
|
+
### Including modules for use in set_up and tear_down
|
158
|
+
|
159
|
+
To use RSpec's `allow()` syntax, include `RSpec::Mocks::ExampleMethods` in the configuration.
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
Pact.configure do | config |
|
163
|
+
config.include RSpec::Mocks::ExampleMethods
|
164
|
+
end
|
165
|
+
```
|
166
|
+
|
167
|
+
Any modules included this way will be available in the set_up and tear_down blocks eg. test data helper methods.
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
Pact.configure do | config |
|
171
|
+
config.include MyFactoryMethods
|
172
|
+
end
|
173
|
+
```
|