pact 1.0.39 → 1.1.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +1 -17
- data/Gemfile.lock +2 -2
- data/README.md +18 -27
- data/bethtest.rb +30 -0
- data/documentation/faq.md +6 -36
- data/lib/pact/configuration.rb +0 -4
- data/lib/pact/consumer/consumer_contract_builder.rb +1 -1
- data/lib/pact/consumer_contract/active_support_support.rb +0 -4
- data/lib/pact/matchers/diff_decorator.rb +1 -1
- data/lib/pact/matchers/index_not_found.rb +12 -3
- data/lib/pact/matchers/matchers.rb +2 -1
- data/lib/pact/matchers/nested_json_diff_decorator.rb +48 -0
- data/lib/pact/matchers/plus_minus_diff_decorator.rb +92 -0
- data/lib/pact/matchers/unexpected_index.rb +13 -3
- data/lib/pact/matchers/unexpected_key.rb +12 -3
- data/lib/pact/provider/configuration.rb +31 -0
- data/lib/pact/provider/matchers.rb +4 -4
- data/lib/pact/provider/pact_spec_runner.rb +28 -35
- data/lib/pact/provider/print_missing_provider_states.rb +2 -2
- data/lib/pact/provider/rspec.rb +7 -16
- data/lib/pact/provider/world.rb +0 -6
- data/lib/pact/shared/key_not_found.rb +16 -3
- data/lib/pact/tasks/task_helper.rb +18 -15
- data/lib/pact/templates/provider_state.erb +1 -0
- data/lib/pact/version.rb +1 -1
- data/spec/lib/pact/matchers/matchers_spec.rb +9 -0
- data/spec/lib/pact/matchers/nested_json_diff_decorator_spec.rb +48 -0
- data/spec/lib/pact/matchers/plus_minus_diff_decorator_spec.rb +186 -0
- data/spec/lib/pact/provider/configuration_spec.rb +131 -102
- data/spec/lib/pact/verification_task_spec.rb +10 -0
- data/spec/support/pact_helper.rb +2 -5
- data/spec/support/stubbing.json +1 -1
- data/spec/support/test_app_fail.json +2 -29
- data/spec/support/test_app_pass.json +4 -4
- data/tasks/pact-test.rake +0 -8
- metadata +13 -22
- data/lib/pact/matchers/difference_indicator.rb +0 -26
- data/lib/pact/provider/rspec/formatter.rb +0 -63
- data/lib/pact/provider/rspec/silent_json_formatter.rb +0 -18
- data/spec/lib/pact/matchers/index_not_found_spec.rb +0 -21
- data/spec/lib/pact/matchers/unexpected_index_spec.rb +0 -20
- data/spec/lib/pact/matchers/unexpected_key_spec.rb +0 -20
- data/spec/lib/pact/shared/key_not_found_spec.rb +0 -20
- data/spec/lib/pact/tasks/task_helper_spec.rb +0 -80
data/CHANGELOG.md
CHANGED
@@ -1,22 +1,6 @@
|
|
1
1
|
Do this to generate your change history
|
2
2
|
|
3
|
-
git log --pretty=format:' * %h - %s (%an, %ad)'
|
4
|
-
|
5
|
-
### 1.0.39 (8 April 2014)
|
6
|
-
|
7
|
-
* a034ab6 - Oh ActiveSupport, why??? Fixing to_json for difference indicators (bethesque, Mon Apr 7 17:26:10 2014 +1000)
|
8
|
-
* 1c7fa0d - Update faq.md (bethesque, Thu Apr 3 09:58:02 2014 +1100)
|
9
|
-
* 8cf5b57 - Update README.md (bethesque, Thu Mar 27 13:38:13 2014 +1100)
|
10
|
-
* 1c5fde9 - Preloading app before suite in pact:verify Ensures consistent behaviour between the first before/after each hooks (bethesque, Thu Mar 27 10:0
|
11
|
-
|
12
|
-
### 1.0.38 (24 March 2014)
|
13
|
-
|
14
|
-
* 7fb2bc3 - Improved readability of pact:verify specs by removing pactfile name and request details from output (bethesque, 23 hours ago)
|
15
|
-
* ff1de3c - Improving readability of error messages when pact:verify fails (bethesque, 23 hours ago)
|
16
|
-
* 8a08abf - Removed the last RSpec private API usage. I think. (bethesque, 33 hours ago)
|
17
|
-
* 6a0be58 - Reducing even more use of RSpec private APIs (bethesque, 33 hours ago)
|
18
|
-
* e1fd51c - Reducing use of RSpec private APIs (bethesque, 34 hours ago)
|
19
|
-
* 587cb90 - Replaced rspec 'commands to rerun failed examples' with Pact specific commands to rerun failed interactions (bethesque, 2 days ago)
|
3
|
+
git log --date=relative --pretty=format:' * %h - %s (%an, %ad)'
|
20
4
|
|
21
5
|
### 1.0.37 (19 March 2014)
|
22
6
|
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
pact (1.0.
|
4
|
+
pact (1.1.0.rc1)
|
5
5
|
awesome_print (~> 1.1)
|
6
6
|
colored
|
7
7
|
find_a_port (~> 1.0.1)
|
@@ -58,7 +58,7 @@ GEM
|
|
58
58
|
rspec-mocks (2.14.3)
|
59
59
|
safe_yaml (0.9.5)
|
60
60
|
slop (3.4.6)
|
61
|
-
thor (0.
|
61
|
+
thor (0.18.1)
|
62
62
|
thread_safe (0.1.3)
|
63
63
|
atomic
|
64
64
|
tzinfo (0.3.38)
|
data/README.md
CHANGED
@@ -28,15 +28,14 @@ Travis CI Status: [![travis-ci.org Build Status](https://travis-ci.org/realestat
|
|
28
28
|
## Why is developing and testing with pacts better than using integration tests?
|
29
29
|
|
30
30
|
* Faster execution.
|
31
|
+
* No need to manage starting and stopping multiple processes.
|
31
32
|
* Reliable responses from mock service provider reduce likelihood of flakey tests.
|
32
33
|
* Only one component is being tested at a time, making the causes of test failures easier to identify.
|
33
34
|
* Design of service provider is improved by considering first how the data is actually going to be used, rather than how it is most easily retrieved and serialised.
|
34
|
-
* No need to manage starting, stopping and fixture set up for multiple applications during a test run.
|
35
35
|
|
36
|
-
##
|
36
|
+
## Google users group
|
37
37
|
|
38
|
-
|
39
|
-
* Google users group: https://groups.google.com/forum/#!forum/pact-support
|
38
|
+
https://groups.google.com/forum/#!forum/pact-support
|
40
39
|
|
41
40
|
## Installation
|
42
41
|
|
@@ -173,22 +172,13 @@ Create your API class using the framework of your choice (e.g. Sinatra, Grape) -
|
|
173
172
|
|
174
173
|
#### 2. Tell your provider that it needs to honour the pact file you made earlier
|
175
174
|
|
176
|
-
Require "pact/tasks" in your Rakefile.
|
177
|
-
|
178
|
-
```ruby
|
179
|
-
# In Rakefile
|
180
|
-
|
181
|
-
require 'pact/tasks'
|
182
|
-
```
|
183
|
-
|
184
175
|
Create a `pact_helper.rb` in your service provider project. The file must be called pact_helper.rb, however there is some flexibility in where it can be stored. The recommended place is `specs/service_consumers/pact_helper.rb`.
|
185
176
|
|
186
177
|
```ruby
|
187
|
-
# In specs/service_consumers/pact_helper.rb
|
188
|
-
|
189
178
|
require 'pact/provider/rspec'
|
190
|
-
# If you wish to use the same spec_helper file as your unit tests, require it here
|
179
|
+
# If you wish to use the same spec_helper file as your unit tests, require it here.
|
191
180
|
# Otherwise, you can set up a separate RSpec configuration in this file just for pact:verify.
|
181
|
+
require './spec_helper'
|
192
182
|
|
193
183
|
Pact.service_provider "My Service Provider" do
|
194
184
|
|
@@ -205,7 +195,13 @@ Pact.service_provider "My Service Provider" do
|
|
205
195
|
end
|
206
196
|
|
207
197
|
```
|
198
|
+
Require "pact/tasks" in your Rakefile. If the pact gem is in the test/development section of your Gemfile, you may want to put an env check around this so it doesn't load the pact tasks in prod.
|
208
199
|
|
200
|
+
```ruby
|
201
|
+
# In Rakefile
|
202
|
+
|
203
|
+
require 'pact/tasks'
|
204
|
+
```
|
209
205
|
|
210
206
|
#### 3. Run your failing specs
|
211
207
|
|
@@ -213,13 +209,11 @@ end
|
|
213
209
|
|
214
210
|
Congratulations! You now have a failing spec to develop against.
|
215
211
|
|
216
|
-
|
212
|
+
#### 4. Implement your service provider
|
217
213
|
|
218
|
-
|
214
|
+
At this stage, you'll probably want to be able to run your specs one at a time while you implement. Define the environment variables PACT_DESCRIPTION and/or PACT_PROVIDER_STATE as so:
|
219
215
|
|
220
|
-
|
221
|
-
|
222
|
-
Rinse and repeat.
|
216
|
+
$ PACT_DESCRIPTION="a request for something" PACT_PROVIDER_STATE="something exists" rake pact:verify
|
223
217
|
|
224
218
|
#### 5. Keep going til you're green
|
225
219
|
|
@@ -255,6 +249,8 @@ my_service.
|
|
255
249
|
will_respond_with(status: 500, :body => {message: "An error occurred"}, :headers => { 'Content-Type' => 'application/json'} )
|
256
250
|
```
|
257
251
|
|
252
|
+
|
253
|
+
|
258
254
|
To define service provider states that create the right data for "a thing exists" and "a thing does not exist", 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.)
|
259
255
|
|
260
256
|
|
@@ -278,7 +274,6 @@ Pact.provider_states_for 'My Service Consumer' do
|
|
278
274
|
|
279
275
|
provider_state "an error occurs while retrieving a thing" do
|
280
276
|
set_up do
|
281
|
-
# Stubbing is ususally the easiest way to generate an error with predictable error text.
|
282
277
|
ThingRepository.stub(:find).and_raise("An error occurred!")
|
283
278
|
end
|
284
279
|
end
|
@@ -382,13 +377,9 @@ Configure the pact_uri in the Pact.service_provider block with the pact artifact
|
|
382
377
|
|
383
378
|
It should run with all your other tests. If an integration is broken, you want to know about it *before* you check in.
|
384
379
|
|
385
|
-
####
|
386
|
-
|
387
|
-
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.
|
388
|
-
|
389
|
-
#### Stub calls to downstream systems
|
380
|
+
#### Use the real database
|
390
381
|
|
391
|
-
|
382
|
+
Do not stub your database calls for pact:verify. This is the best time for you to test your database integration. If you stub your database calls, you are getting little more assurance that the real end-to-end will work than if you'd used a unit test. It's the appropriate time to incur the overhead of a database call.
|
392
383
|
|
393
384
|
## Gotchas
|
394
385
|
|
data/bethtest.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'colored'
|
2
|
+
|
3
|
+
puts "Key: #{'-'.red} expected, #{'+'.green} actual"
|
4
|
+
|
5
|
+
puts '{
|
6
|
+
"_embedded": {'
|
7
|
+
puts '- "events": [
|
8
|
+
- {
|
9
|
+
- "probability": "75",
|
10
|
+
- "timestamp": "2014-03-03T14:00:00+00:00",
|
11
|
+
- "_embedded": {
|
12
|
+
- "opportunity": {
|
13
|
+
- "displayAdvertising": true,
|
14
|
+
- "_links": {
|
15
|
+
- "self": {
|
16
|
+
- "href": "http://media-opportunities/opportunity-id-1"
|
17
|
+
- },
|
18
|
+
- "mediaProject": {
|
19
|
+
- "href": "http://media-projects/project-id-1"
|
20
|
+
- }
|
21
|
+
- }
|
22
|
+
- }
|
23
|
+
- }
|
24
|
+
- }
|
25
|
+
- ]'.red
|
26
|
+
puts '+ {
|
27
|
+
+ }'.green
|
28
|
+
puts ' }
|
29
|
+
}'
|
30
|
+
|
data/documentation/faq.md
CHANGED
@@ -1,24 +1,6 @@
|
|
1
1
|
# Frequently asked questions
|
2
2
|
|
3
|
-
|
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?
|
3
|
+
## How can I verify a pact against a non-ruby provider?
|
22
4
|
|
23
5
|
You can verify a pact against any running server, regardless of language, using [pact-provider-proxy](https://github.com/bethesque/pact-provider-proxy).
|
24
6
|
|
@@ -30,20 +12,13 @@ Become famous, and write a pact-consumer library yourself! Then let us know abou
|
|
30
12
|
|
31
13
|
### How can I specify hooks to be executed before/after all examples for pact:verify?
|
32
14
|
|
33
|
-
|
15
|
+
The pact:verify RSpec examples have the metadata `{:pact => :verify}` defined. You can add RSpec hooks using a filter as shown here:
|
34
16
|
|
35
17
|
```ruby
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
set_up do
|
40
|
-
# Set up code here
|
41
|
-
end
|
42
|
-
|
43
|
-
tear_down do
|
44
|
-
# tear down code here
|
18
|
+
RSpec.configure do | config |
|
19
|
+
config.before :each, :pact => :verify do
|
20
|
+
# Your code here
|
45
21
|
end
|
46
|
-
|
47
22
|
end
|
48
23
|
```
|
49
24
|
|
@@ -66,10 +41,5 @@ This is a hotly debated issue.
|
|
66
41
|
|
67
42
|
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.
|
68
43
|
|
69
|
-
However, if you have a large and complex provider, you might decide to stub some of your application code.
|
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).
|
44
|
+
However, if you have a large and complex provider, you might decide to stub some of your application code. If you do, remember to add your own "verification" of your stubbed methods - write another test that will break if the behaviour of the stubbed methods changes.
|
74
45
|
|
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.
|
data/lib/pact/configuration.rb
CHANGED
@@ -10,8 +10,6 @@ module Pact
|
|
10
10
|
attr_accessor :tmp_dir
|
11
11
|
attr_accessor :reports_dir
|
12
12
|
attr_writer :pactfile_write_mode
|
13
|
-
attr_accessor :error_stream
|
14
|
-
attr_accessor :output_stream
|
15
13
|
|
16
14
|
def log_path
|
17
15
|
log_dir + "/pact.log"
|
@@ -56,8 +54,6 @@ module Pact
|
|
56
54
|
c.logger = default_logger c.log_path
|
57
55
|
c.pactfile_write_mode = :overwrite
|
58
56
|
c.reports_dir = File.expand_path('./reports/pacts')
|
59
|
-
c.output_stream = $stdout
|
60
|
-
c.error_stream = $stderr
|
61
57
|
c
|
62
58
|
end
|
63
59
|
|
@@ -1,12 +1,21 @@
|
|
1
|
-
require 'pact/matchers/difference_indicator'
|
2
|
-
|
3
1
|
module Pact
|
4
|
-
class IndexNotFound
|
2
|
+
class IndexNotFound
|
3
|
+
def == other
|
4
|
+
other.is_a? IndexNotFound
|
5
|
+
end
|
5
6
|
|
6
7
|
def to_s
|
7
8
|
"<index not found>"
|
8
9
|
end
|
9
10
|
|
11
|
+
def to_json options = {}
|
12
|
+
as_json.to_json options
|
13
|
+
end
|
14
|
+
|
15
|
+
def as_json options = {}
|
16
|
+
to_s
|
17
|
+
end
|
18
|
+
|
10
19
|
def empty?
|
11
20
|
true
|
12
21
|
end
|
@@ -40,7 +40,7 @@ module Pact
|
|
40
40
|
|
41
41
|
class NoDiffIndicator
|
42
42
|
|
43
|
-
def to_json
|
43
|
+
def to_json options = {}
|
44
44
|
to_s
|
45
45
|
end
|
46
46
|
|
@@ -58,6 +58,7 @@ module Pact
|
|
58
58
|
DEFAULT_OPTIONS = {allow_unexpected_keys: true, structure: false}.freeze
|
59
59
|
|
60
60
|
def diff expected, actual, opts = {}
|
61
|
+
# require 'pry'; pry(binding);
|
61
62
|
options = DEFAULT_OPTIONS.merge(opts)
|
62
63
|
case expected
|
63
64
|
when Hash then hash_diff(expected, actual, options)
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'pact/consumer_contract/active_support_support'
|
2
|
+
require 'colored'
|
3
|
+
|
4
|
+
module Pact
|
5
|
+
module Matchers
|
6
|
+
class NestedJsonDiffDecorator
|
7
|
+
|
8
|
+
include Pact::ActiveSupportSupport
|
9
|
+
|
10
|
+
EXPECTED = '"EXPECTED"'
|
11
|
+
EXPECTED_COLOURED = '"' + "expected".red + '"'
|
12
|
+
ACTUAL = '"ACTUAL"'
|
13
|
+
ACTUAL_COLOURED = '"' + "actual".green + '"'
|
14
|
+
|
15
|
+
attr_reader :diff
|
16
|
+
|
17
|
+
def initialize diff
|
18
|
+
@diff = diff
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_hash
|
22
|
+
diff
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
colourise_message_if_configured fix_json_formatting(diff.to_json)
|
27
|
+
end
|
28
|
+
|
29
|
+
def colourise_message_if_configured message
|
30
|
+
if Pact.configuration.color_enabled
|
31
|
+
colourise_message message
|
32
|
+
else
|
33
|
+
message
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def colourise_message message
|
38
|
+
message.split("\n").collect{| line | colourise(line) }.join("\n")
|
39
|
+
end
|
40
|
+
|
41
|
+
def colourise line
|
42
|
+
line.white.gsub(EXPECTED, EXPECTED_COLOURED).gsub(ACTUAL, ACTUAL_COLOURED)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module Pact
|
2
|
+
module Matchers
|
3
|
+
|
4
|
+
class PlusMinusDiffDecorator
|
5
|
+
|
6
|
+
def initialize diff
|
7
|
+
@diff = diff
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
expected = generate_string(diff, :expected)
|
12
|
+
actual = generate_string(diff, :actual)
|
13
|
+
|
14
|
+
RSpec::Expectations::Differ.new.diff_as_string actual, expected
|
15
|
+
end
|
16
|
+
|
17
|
+
def handle thing, target
|
18
|
+
case thing
|
19
|
+
when Hash then copy_hash(thing, target)
|
20
|
+
when Array then copy_array(thing, target)
|
21
|
+
when Difference then copy_diff(thing, target)
|
22
|
+
when NoDiffIndicator then copy_no_diff(thing, target)
|
23
|
+
else copy_object(thing, target)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def generate_string diff, target
|
30
|
+
comparable = handle(diff, target)
|
31
|
+
begin
|
32
|
+
# Can't think of an elegant way to check if we can pretty generate other than to try it and maybe fail
|
33
|
+
JSON.pretty_generate(comparable)
|
34
|
+
rescue JSON::GeneratorError
|
35
|
+
comparable.to_s
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def copy_hash hash, target
|
40
|
+
hash.keys.each_with_object({}) do | key, new_hash |
|
41
|
+
value = handle hash[key], target
|
42
|
+
new_hash[key] = value unless (KeyNotFound === value || UnexpectedKey === value)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def copy_array array, target
|
47
|
+
array.each_index.each_with_object([]) do | index, new_array |
|
48
|
+
value = handle array[index], target
|
49
|
+
new_array[index] = value unless (UnexpectedIndex === value || IndexNotFound === value)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def copy_no_diff(thing, target)
|
54
|
+
thing
|
55
|
+
end
|
56
|
+
|
57
|
+
def copy_diff difference, target
|
58
|
+
if target == :actual
|
59
|
+
copy_object difference.actual, target
|
60
|
+
else
|
61
|
+
copy_object difference.expected, target
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def copy_object object, target
|
66
|
+
if Regexp === object
|
67
|
+
RegexpDecorator.new(object)
|
68
|
+
else
|
69
|
+
object
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class RegexpDecorator
|
74
|
+
|
75
|
+
def initialize regexp
|
76
|
+
@regexp = regexp
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_json options={}
|
80
|
+
@regexp.inspect
|
81
|
+
end
|
82
|
+
|
83
|
+
def as_json
|
84
|
+
@regexp.inspect
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
attr_reader :diff
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|