pact 0.1.28 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (285) hide show
  1. data/.gitignore +2 -1
  2. data/.ruby-version +1 -0
  3. data/.travis.yml +8 -0
  4. data/CHANGELOG.md +374 -0
  5. data/Gemfile +0 -1
  6. data/Gemfile.lock +58 -55
  7. data/README.md +214 -139
  8. data/Rakefile +4 -28
  9. data/bin/pact +2 -2
  10. data/config.ru +3 -0
  11. data/documentation/README.md +13 -0
  12. data/documentation/configuration.md +166 -0
  13. data/documentation/diff_formatter_embedded.png +0 -0
  14. data/documentation/diff_formatter_list.png +0 -0
  15. data/documentation/diff_formatter_unix.png +0 -0
  16. data/example/animal-service/Gemfile +14 -0
  17. data/example/animal-service/Gemfile.lock +69 -0
  18. data/example/animal-service/Rakefile +5 -0
  19. data/example/animal-service/config.ru +3 -0
  20. data/example/animal-service/db/animal_db.sqlite3 +0 -0
  21. data/example/animal-service/lib/animal_service/animal_repository.rb +12 -0
  22. data/example/animal-service/lib/animal_service/api.rb +28 -0
  23. data/example/animal-service/lib/animal_service/db.rb +5 -0
  24. data/example/animal-service/spec/service_consumers/pact_helper.rb +11 -0
  25. data/example/animal-service/spec/service_consumers/provider_states_for_zoo_app.rb +26 -0
  26. data/example/zoo-app/Gemfile +12 -0
  27. data/example/zoo-app/Gemfile.lock +63 -0
  28. data/example/zoo-app/Rakefile +5 -0
  29. data/example/zoo-app/doc/pacts/markdown/README.md +3 -0
  30. data/example/zoo-app/doc/pacts/markdown/Zoo App - Animal Service.md +75 -0
  31. data/example/zoo-app/lib/zoo_app/animal_service_client.rb +40 -0
  32. data/example/zoo-app/lib/zoo_app/models/alligator.rb +15 -0
  33. data/example/zoo-app/spec/pacts/zoo_app-animal_service.json +67 -0
  34. data/example/zoo-app/spec/service_providers/animal_service_client_spec.rb +71 -0
  35. data/example/zoo-app/spec/service_providers/pact_helper.rb +15 -0
  36. data/example/zoo-app/spec/spec_helper.rb +6 -0
  37. data/lib/pact/cli.rb +138 -0
  38. data/lib/pact/configuration.rb +169 -28
  39. data/lib/pact/consumer/app_manager.rb +10 -29
  40. data/lib/pact/consumer/configuration/configuration_extensions.rb +15 -0
  41. data/lib/pact/consumer/configuration/dsl.rb +12 -0
  42. data/lib/pact/consumer/configuration/mock_service.rb +91 -0
  43. data/lib/pact/consumer/configuration/service_consumer.rb +51 -0
  44. data/lib/pact/consumer/configuration/service_provider.rb +40 -0
  45. data/lib/pact/consumer/configuration.rb +11 -0
  46. data/lib/pact/consumer/consumer_contract_builder.rb +44 -47
  47. data/lib/pact/consumer/interaction_builder.rb +41 -0
  48. data/lib/pact/consumer/interactions_filter.rb +48 -0
  49. data/lib/pact/consumer/mock_service/app.rb +82 -0
  50. data/lib/pact/consumer/mock_service/interaction_delete.rb +33 -0
  51. data/lib/pact/consumer/mock_service/interaction_list.rb +76 -0
  52. data/lib/pact/consumer/mock_service/interaction_mismatch.rb +73 -0
  53. data/lib/pact/consumer/mock_service/interaction_post.rb +31 -0
  54. data/lib/pact/consumer/mock_service/interaction_replay.rb +139 -0
  55. data/lib/pact/consumer/mock_service/log_get.rb +28 -0
  56. data/lib/pact/consumer/mock_service/missing_interactions_get.rb +30 -0
  57. data/lib/pact/consumer/mock_service/mock_service_administration_endpoint.rb +31 -0
  58. data/lib/pact/consumer/mock_service/pact_post.rb +33 -0
  59. data/lib/pact/consumer/mock_service/rack_request_helper.rb +51 -0
  60. data/lib/pact/consumer/mock_service/verification_get.rb +68 -0
  61. data/lib/pact/consumer/mock_service.rb +1 -339
  62. data/lib/pact/consumer/mock_service_client.rb +65 -0
  63. data/lib/pact/consumer/mock_service_interaction_expectation.rb +37 -0
  64. data/lib/pact/consumer/request.rb +27 -0
  65. data/lib/pact/consumer/rspec.rb +9 -19
  66. data/lib/pact/consumer/server.rb +90 -0
  67. data/lib/pact/consumer/spec_hooks.rb +38 -0
  68. data/lib/pact/consumer/world.rb +37 -0
  69. data/lib/pact/consumer.rb +10 -7
  70. data/lib/pact/consumer_contract/consumer_contract.rb +115 -0
  71. data/lib/pact/consumer_contract/consumer_contract_writer.rb +84 -0
  72. data/lib/pact/consumer_contract/file_name.rb +19 -0
  73. data/lib/pact/consumer_contract/headers.rb +51 -0
  74. data/lib/pact/consumer_contract/interaction.rb +67 -0
  75. data/lib/pact/consumer_contract/pact_file.rb +24 -0
  76. data/lib/pact/consumer_contract/request.rb +73 -0
  77. data/lib/pact/consumer_contract/service_consumer.rb +28 -0
  78. data/lib/pact/consumer_contract/service_provider.rb +28 -0
  79. data/lib/pact/consumer_contract.rb +1 -110
  80. data/lib/pact/doc/doc_file.rb +40 -0
  81. data/lib/pact/doc/generate.rb +11 -0
  82. data/lib/pact/doc/generator.rb +82 -0
  83. data/lib/pact/doc/interaction_view_model.rb +114 -0
  84. data/lib/pact/doc/markdown/consumer_contract_renderer.rb +56 -0
  85. data/lib/pact/doc/markdown/generator.rb +26 -0
  86. data/lib/pact/doc/markdown/index_renderer.rb +41 -0
  87. data/lib/pact/doc/markdown/interaction.erb +14 -0
  88. data/lib/pact/doc/markdown/interaction_renderer.rb +38 -0
  89. data/lib/pact/doc/sort_interactions.rb +17 -0
  90. data/lib/pact/matchers/actual_type.rb +16 -0
  91. data/lib/pact/matchers/base_difference.rb +37 -0
  92. data/lib/pact/matchers/differ.rb +153 -0
  93. data/lib/pact/matchers/difference.rb +13 -0
  94. data/lib/pact/matchers/difference_indicator.rb +26 -0
  95. data/lib/pact/matchers/embedded_diff_formatter.rb +62 -0
  96. data/lib/pact/matchers/expected_type.rb +35 -0
  97. data/lib/pact/matchers/index_not_found.rb +15 -0
  98. data/lib/pact/matchers/list_diff_formatter.rb +101 -0
  99. data/lib/pact/matchers/matchers.rb +91 -37
  100. data/lib/pact/matchers/no_diff_indicator.rb +18 -0
  101. data/lib/pact/matchers/regexp_difference.rb +13 -0
  102. data/lib/pact/matchers/type_difference.rb +16 -0
  103. data/lib/pact/matchers/unexpected_index.rb +11 -0
  104. data/lib/pact/matchers/unexpected_key.rb +11 -0
  105. data/lib/pact/matchers/unix_diff_formatter.rb +114 -0
  106. data/lib/pact/project_root.rb +7 -0
  107. data/lib/pact/provider/configuration/configuration_extension.rb +58 -0
  108. data/lib/pact/provider/configuration/dsl.rb +13 -0
  109. data/lib/pact/provider/configuration/pact_verification.rb +46 -0
  110. data/lib/pact/provider/configuration/service_provider_config.rb +16 -0
  111. data/lib/pact/provider/configuration/service_provider_dsl.rb +59 -0
  112. data/lib/pact/provider/configuration.rb +7 -0
  113. data/lib/pact/provider/context.rb +0 -0
  114. data/lib/pact/provider/matchers/messages.rb +45 -0
  115. data/lib/pact/provider/pact_helper_locator.rb +22 -0
  116. data/lib/pact/provider/pact_spec_runner.rb +123 -0
  117. data/lib/pact/provider/pact_verification.rb +17 -0
  118. data/lib/pact/provider/print_missing_provider_states.rb +30 -0
  119. data/lib/pact/provider/request.rb +70 -0
  120. data/lib/pact/provider/rspec/backtrace_formatter.rb +43 -0
  121. data/lib/pact/provider/rspec/custom_options_file +0 -0
  122. data/lib/pact/provider/rspec/formatter_rspec_2.rb +65 -0
  123. data/lib/pact/provider/rspec/formatter_rspec_3.rb +96 -0
  124. data/lib/pact/provider/rspec/matchers.rb +97 -0
  125. data/lib/pact/provider/rspec.rb +181 -0
  126. data/lib/pact/provider/state/provider_state.rb +181 -0
  127. data/lib/pact/provider/state/provider_state_configured_modules.rb +13 -0
  128. data/lib/pact/provider/state/provider_state_manager.rb +42 -0
  129. data/lib/pact/provider/state/provider_state_proxy.rb +39 -0
  130. data/lib/pact/provider/test_methods.rb +47 -0
  131. data/lib/pact/provider/verification_report.rb +36 -0
  132. data/lib/pact/provider/world.rb +23 -0
  133. data/lib/pact/provider.rb +3 -0
  134. data/lib/pact/reification.rb +6 -4
  135. data/lib/pact/rspec.rb +53 -0
  136. data/lib/pact/shared/active_support_support.rb +51 -0
  137. data/lib/pact/shared/dsl.rb +76 -0
  138. data/lib/pact/shared/jruby_support.rb +18 -0
  139. data/lib/pact/shared/json_differ.rb +15 -0
  140. data/lib/pact/shared/key_not_found.rb +15 -0
  141. data/lib/pact/shared/null_expectation.rb +31 -0
  142. data/lib/pact/shared/request.rb +80 -0
  143. data/lib/pact/shared/text_differ.rb +14 -0
  144. data/lib/pact/something_like.rb +49 -0
  145. data/lib/pact/symbolize_keys.rb +12 -0
  146. data/lib/pact/tasks/task_helper.rb +36 -0
  147. data/lib/pact/tasks/verification_task.rb +87 -0
  148. data/lib/pact/tasks.rb +2 -0
  149. data/lib/pact/templates/provider_state.erb +14 -0
  150. data/lib/pact/term.rb +47 -2
  151. data/lib/pact/version.rb +1 -1
  152. data/lib/pact.rb +1 -1
  153. data/lib/tasks/pact.rake +25 -2
  154. data/pact.gemspec +13 -11
  155. data/{scratchpad.txt → scratchpad.rb} +18 -2
  156. data/spec/features/consumption_spec.rb +24 -56
  157. data/spec/features/production_spec.rb +19 -24
  158. data/spec/features/{producer_states → provider_states}/zebras.rb +3 -3
  159. data/spec/integration/consumer_spec.rb +212 -0
  160. data/spec/integration/pact/consumer_configuration_spec.rb +66 -0
  161. data/spec/integration/pact/provider_configuration_spec.rb +25 -0
  162. data/spec/lib/pact/cli_spec.rb +47 -0
  163. data/spec/lib/pact/configuration_spec.rb +269 -7
  164. data/spec/lib/pact/consumer/app_manager_spec.rb +3 -3
  165. data/spec/lib/pact/consumer/configuration_spec.rb +57 -0
  166. data/spec/lib/pact/consumer/consumer_contract_builder_spec.rb +52 -70
  167. data/spec/lib/pact/consumer/interaction_builder_spec.rb +91 -0
  168. data/spec/lib/pact/consumer/interactions_spec.rb +64 -0
  169. data/spec/lib/pact/consumer/mock_service/app_spec.rb +52 -0
  170. data/spec/lib/pact/consumer/mock_service/interaction_list_spec.rb +78 -0
  171. data/spec/lib/pact/consumer/mock_service/interaction_mismatch_spec.rb +70 -0
  172. data/spec/lib/pact/consumer/mock_service/interaction_replay_spec.rb +12 -0
  173. data/spec/lib/pact/consumer/mock_service/rack_request_helper_spec.rb +88 -0
  174. data/spec/lib/pact/consumer/mock_service/verification_get_spec.rb +142 -0
  175. data/spec/lib/pact/consumer/mock_service_client_spec.rb +88 -0
  176. data/spec/lib/pact/consumer/mock_service_interaction_expectation_spec.rb +54 -0
  177. data/spec/lib/pact/consumer/request_spec.rb +24 -0
  178. data/spec/lib/pact/consumer/service_consumer_spec.rb +1 -1
  179. data/spec/lib/pact/consumer_contract/active_support_support_spec.rb +58 -0
  180. data/spec/lib/pact/consumer_contract/consumer_contract_spec.rb +180 -0
  181. data/spec/lib/pact/consumer_contract/consumer_contract_writer_spec.rb +111 -0
  182. data/spec/lib/pact/consumer_contract/headers_spec.rb +107 -0
  183. data/spec/lib/pact/consumer_contract/interaction_spec.rb +107 -0
  184. data/spec/lib/pact/{request_spec.rb → consumer_contract/request_spec.rb} +80 -67
  185. data/spec/lib/pact/doc/generator_spec.rb +84 -0
  186. data/spec/lib/pact/doc/interaction_view_model_spec.rb +132 -0
  187. data/spec/lib/pact/doc/markdown/consumer_contract_renderer_spec.rb +29 -0
  188. data/spec/lib/pact/doc/markdown/index_renderer_spec.rb +29 -0
  189. data/spec/lib/pact/matchers/differ_spec.rb +214 -0
  190. data/spec/lib/pact/matchers/difference_spec.rb +22 -0
  191. data/spec/lib/pact/matchers/embedded_diff_formatter_spec.rb +90 -0
  192. data/spec/lib/pact/matchers/index_not_found_spec.rb +21 -0
  193. data/spec/lib/pact/matchers/list_diff_formatter_spec.rb +114 -0
  194. data/spec/lib/pact/matchers/matchers_spec.rb +400 -254
  195. data/spec/lib/pact/matchers/regexp_difference_spec.rb +20 -0
  196. data/spec/lib/pact/matchers/type_difference_spec.rb +34 -0
  197. data/spec/lib/pact/matchers/unexpected_index_spec.rb +20 -0
  198. data/spec/lib/pact/matchers/unexpected_key_spec.rb +20 -0
  199. data/spec/lib/pact/matchers/unix_diff_formatter_spec.rb +216 -0
  200. data/spec/lib/pact/provider/configuration/configuration_extension_spec.rb +30 -0
  201. data/spec/lib/pact/provider/configuration/pact_verification_spec.rb +43 -0
  202. data/spec/lib/pact/provider/configuration/service_provider_config_spec.rb +21 -0
  203. data/spec/lib/pact/provider/configuration/service_provider_dsl_spec.rb +108 -0
  204. data/spec/lib/pact/provider/configuration_spec.rb +50 -0
  205. data/spec/lib/pact/provider/matchers/messages_spec.rb +116 -0
  206. data/spec/lib/pact/provider/pact_helper_locator_spec.rb +54 -0
  207. data/spec/lib/pact/provider/print_missing_provider_states_spec.rb +19 -0
  208. data/spec/lib/pact/provider/request_spec.rb +78 -0
  209. data/spec/lib/pact/provider/rspec/formatter_rspec_2_spec.rb +68 -0
  210. data/spec/lib/pact/provider/rspec/formatter_rspec_3_spec.rb +72 -0
  211. data/spec/lib/pact/provider/rspec_spec.rb +55 -0
  212. data/spec/lib/pact/provider/state/provider_state_manager_spec.rb +89 -0
  213. data/spec/lib/pact/provider/state/provider_state_proxy_spec.rb +80 -0
  214. data/spec/lib/pact/provider/state/provider_state_spec.rb +213 -0
  215. data/spec/lib/pact/provider/world_spec.rb +41 -0
  216. data/spec/lib/pact/reification_spec.rb +24 -0
  217. data/spec/lib/pact/shared/dsl_spec.rb +86 -0
  218. data/spec/lib/pact/shared/json_differ_spec.rb +36 -0
  219. data/spec/lib/pact/shared/key_not_found_spec.rb +20 -0
  220. data/spec/lib/pact/shared/request_spec.rb +111 -0
  221. data/spec/lib/pact/shared/text_differ_spec.rb +54 -0
  222. data/spec/lib/pact/something_like_spec.rb +21 -0
  223. data/spec/lib/pact/tasks/task_helper_spec.rb +74 -0
  224. data/spec/lib/pact/tasks/verification_task_spec.rb +75 -0
  225. data/spec/lib/pact/term_spec.rb +57 -4
  226. data/spec/pact_specification/compliance-1.0.0.rb +47 -0
  227. data/spec/spec_helper.rb +17 -0
  228. data/spec/standalone/consumer_fail_test.rb +55 -0
  229. data/spec/standalone/consumer_pass_test.rb +51 -0
  230. data/spec/support/a_consumer-a_producer.json +2 -4
  231. data/spec/support/a_consumer-a_provider.json +32 -0
  232. data/spec/support/active_support_if_configured.rb +6 -0
  233. data/spec/support/app_for_config_ru.rb +4 -0
  234. data/spec/support/consumer_contract_template.json +24 -0
  235. data/spec/support/dsl_spec_support.rb +7 -0
  236. data/spec/support/factories.rb +82 -0
  237. data/spec/support/generated_index.md +4 -0
  238. data/spec/support/generated_markdown.md +55 -0
  239. data/spec/support/interaction_view_model.json +63 -0
  240. data/spec/support/interaction_view_model_with_terms.json +50 -0
  241. data/spec/support/markdown_pact.json +48 -0
  242. data/spec/support/missing_provider_states_output.txt +25 -0
  243. data/spec/support/options.json +21 -0
  244. data/spec/support/options_app.rb +15 -0
  245. data/spec/support/pact_helper.rb +57 -0
  246. data/spec/support/shared_examples_for_request.rb +94 -0
  247. data/spec/support/spec_support.rb +20 -0
  248. data/spec/support/stubbing.json +22 -0
  249. data/spec/support/stubbing_using_allow.rb +29 -0
  250. data/spec/support/term.json +48 -0
  251. data/spec/support/test_app_fail.json +41 -2
  252. data/spec/support/test_app_pass.json +19 -2
  253. data/spec/support/test_app_with_right_content_type_differ.json +23 -0
  254. data/tasks/pact-test.rake +98 -8
  255. data/tasks/spec.rake +8 -0
  256. metadata +393 -95
  257. data/lib/pact/app.rb +0 -32
  258. data/lib/pact/consumer/configuration_dsl.rb +0 -71
  259. data/lib/pact/consumer/dsl.rb +0 -98
  260. data/lib/pact/consumer/interaction.rb +0 -70
  261. data/lib/pact/consumer/run_condor.rb +0 -4
  262. data/lib/pact/consumer/run_mock_contract_service.rb +0 -13
  263. data/lib/pact/consumer/service_consumer.rb +0 -22
  264. data/lib/pact/consumer/service_producer.rb +0 -23
  265. data/lib/pact/json_warning.rb +0 -23
  266. data/lib/pact/producer/configuration_dsl.rb +0 -62
  267. data/lib/pact/producer/matchers.rb +0 -22
  268. data/lib/pact/producer/pact_spec_runner.rb +0 -57
  269. data/lib/pact/producer/producer_state.rb +0 -81
  270. data/lib/pact/producer/rspec.rb +0 -127
  271. data/lib/pact/producer/test_methods.rb +0 -89
  272. data/lib/pact/producer.rb +0 -1
  273. data/lib/pact/rake_task.rb +0 -64
  274. data/lib/pact/request.rb +0 -109
  275. data/lib/pact/verification_task.rb +0 -57
  276. data/spec/integration/pact/configuration_spec.rb +0 -65
  277. data/spec/lib/pact/consumer/dsl_spec.rb +0 -52
  278. data/spec/lib/pact/consumer/interaction_spec.rb +0 -108
  279. data/spec/lib/pact/consumer/mock_service_spec.rb +0 -147
  280. data/spec/lib/pact/consumer_contract_spec.rb +0 -125
  281. data/spec/lib/pact/producer/configuration_dsl_spec.rb +0 -101
  282. data/spec/lib/pact/producer/producer_state_spec.rb +0 -103
  283. data/spec/lib/pact/producer/rspec_spec.rb +0 -48
  284. data/spec/lib/pact/verification_task_spec.rb +0 -64
  285. data/spec/support/pact_rake_support.rb +0 -41
data/README.md CHANGED
@@ -1,13 +1,55 @@
1
1
  # Pact
2
2
 
3
- Define a pact between service consumers and providers.
3
+ Define a pact between service consumers and providers, enabling "consumer driven contract" testing.
4
4
 
5
+ Pact provides an fluent API 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 consumer 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.
5
6
 
6
- Pact provides an RSpec DSL for service consumers to define the request they will make to a service producer and the
7
- response they expect back. This expectation is used in the consumers specs to provide a mock producer, and is also
8
- played back in the producer specs to ensure the producer actually does provide the response the consumer expects.
7
+ This allows testing of both sides of an integration point using fast unit tests.
9
8
 
10
- This allows you to test both sides of an integration point using fast unit tests.
9
+ This gem is inspired by the concept of "Consumer driven contracts". See [this article](http://martinfowler.com/articles/consumerDrivenContracts.html) by Martin Fowler for more information.
10
+
11
+ Travis CI Status: [![travis-ci.org Build Status](https://travis-ci.org/realestate-com-au/pact.png)](https://travis-ci.org/realestate-com-au/pact)
12
+
13
+ ## What is it good for?
14
+
15
+ Pact is most valuable for designing and testing integrations where you (or your team/organisation/partner organisation) control the development of both the consumer and the provider. It is fantastic tool for testing intra-organsation microservices.
16
+
17
+ ## What is it not good for?
18
+
19
+ * Performance and load testing.
20
+ * Functional testing of the provider - that is what the provider's own tests should do. Pact is about checking the contents and format of requests and responses.
21
+ * Situations where you cannot load data into the provider without using the API that you're actually testing.
22
+
23
+ ## Features
24
+
25
+ * A service is mocked using an actual process running on a specified port, so javascript clients can be tested as easily as backend clients.
26
+ * "Provider states" (similar to fixtures) allow the same request to be made with a different expected response.
27
+ * Consumers specify only the fields they are interested in, allowing a provider to return more fields without breaking the pact. This allows a provider to have a different pact with a different consumer, and know which fields each cares about in a given response.
28
+ * RSpec and Minitest support for the service consumer codebase.
29
+ * Rake tasks allow pacts to be verified against a service provider codebase.
30
+ * Different versions of a consumer/provider pairs can be easily tested against each other, allowing confidence when deploying new versions of each (see the pact_broker and pact_broker-client gems).
31
+ * Autogenerated API documentation - need we say more?
32
+ * Autogenerated network diagrams with the [Pact Broker](https://github.com/bethesque/pact_broker)
33
+
34
+ ## How does it work?
35
+
36
+ 1. In the specs for the provider facing code in the consumer project, expectations are set up on a mock service provider.
37
+ 1. When the specs are run, the mock service returns the expected responses. The requests, and their expected responses, are then written to a "pact" file.
38
+ 1. The requests in the pact file are later replayed against the provider, and the actual responses are checked to make sure they match the expected responses.
39
+
40
+ ## Why is developing and testing with Pact better than using traditional system integration tests?
41
+
42
+ * Faster execution.
43
+ * Reliable responses from mock service reduce likelihood of flakey tests.
44
+ * Causes of failure are easier to identify as only one component is being tested at a time.
45
+ * 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.
46
+ * No separate integration environment required for automated integration tests - pact tests run in standalone CI builds.
47
+ * Integration flows that would traditionally require running multiple services at the same time can be broken down and each integration point tested separately.
48
+
49
+ ## Contact
50
+
51
+ * Twitter: [@pact_up](https://twitter.com/pact_up)
52
+ * Google users group: https://groups.google.com/forum/#!forum/pact-support
11
53
 
12
54
  ## Installation
13
55
 
@@ -15,220 +57,247 @@ Put it in your Gemfile. You know how.
15
57
 
16
58
  ## Usage
17
59
 
18
- ### Consumer project
60
+ ### Service Consumer project
61
+
62
+ #### 1. Start with you model
63
+
64
+ Imagine a model class that looks something like this. The attributes for a Something live on a remote server, and will need to be retrieved by an HTTP call.
65
+
66
+ ```ruby
67
+ class Something
68
+ attr_reader :name
19
69
 
20
- #### Configuration
70
+ def initialize name
71
+ @name = name
72
+ end
21
73
 
22
- Pact.configure do | config |
23
- config.pact_dir = "???" # Optional, default is ./spec/pacts
24
- config.log_dir = "???" # Optional, default is ./log
25
- config.logger = "??"
26
- config.logger.level = Logger::DEBUG #By default this is INFO, bump this up to debug for more detailed logs
74
+ def == other
75
+ other.is_a?(Something) && other.name == name
76
+ end
27
77
  end
78
+ ```
79
+
80
+ #### 2. Create a skeleton client class
28
81
 
29
- #### Create a Consumer (Driven) Contract
82
+ Imagine a service provider client class that looks something like this.
30
83
 
31
84
  ```ruby
32
- require 'pact/consumer/rspec'
85
+ require 'httparty'
33
86
 
34
- class SomeServiceClient
87
+ class MyServiceProviderClient
35
88
  include HTTParty
36
- # Load your base_uri from a stub-able source
37
- base_uri App.configuration.some_service_base_uri
89
+ base_uri 'http://my-service'
38
90
 
39
91
  def get_something
40
- JSON.parse(self.class.get("/something").body)
92
+ # Yet to be implemented because we're doing Test First Development...
41
93
  end
42
94
  end
95
+ ```
96
+ #### 3. Configure the mock server
97
+
98
+ The following code will create a mock service on localhost:1234 which will respond to your application's queries over HTTP as if it were the real "My Service Provider" app. It also creats a mock service provider object which you will use to set up your expectations. The method name to access the mock service provider will be what ever name you give as the service argument - in this case "my_service_provider"
43
99
 
44
- Pact.configure do | config |
45
- config.consumer do
46
- name 'My Consumer'
47
- end
48
- end
49
100
 
50
- # The following block creates a service on localhost:1234 which will respond to your application's queries
51
- # over HTTP as if it were the real "My Producer" app. It also creats a mock producer object
52
- # which you will use to set up your expectations. The method name to access the mock producer
53
- # will be what ever name you give as the service argument - in this case "my_producer"
101
+ ```ruby
102
+ # In /spec/service_providers/pact_helper.rb
54
103
 
55
- Pact.with_producer "My Producer" do
56
- mock_service :my_producer do
57
- port 1234
104
+ require 'pact/consumer/rspec'
105
+ # or require 'pact/consumer/minitest' if you are using Minitest
106
+
107
+ Pact.service_consumer "My Service Consumer" do
108
+ has_pact_with "My Service Provider" do
109
+ mock_service :my_service_provider do
110
+ port 1234
111
+ end
58
112
  end
59
113
  end
114
+ ```
60
115
 
61
- # Use the :pact => true describe metadata to include all the pact generation functionality in your spec.
116
+ #### 4. Write a failing spec for the client
62
117
 
63
- describe "a pact with My Producer", :pact => true do
118
+ ```ruby
119
+ # In /spec/service_providers/my_service_provider_client_spec.rb
120
+
121
+ # When using RSpec, use the metadata `:pact => true` to include all the pact functionality in your spec.
122
+ # When using Minitest, include Pact::Consumer::Minitest in your spec.
123
+
124
+ describe MyServiceProviderClient, :pact => true do
64
125
 
65
126
  before do
66
127
  # Configure your client to point to the stub service on localhost using the port you have specified
67
- Application.configuration.stub(:some_service_base_uri).and_return('localhost:1234')
128
+ MyServiceProviderClient.base_uri 'localhost:1234'
68
129
  end
69
130
 
70
- it "returns something when requested" do
71
- my_producer.
72
- given("something exists").
73
- upon_receiving("a request for something").
74
- with({ method: :get, path: '/something' }).
75
- will_respond_with({
76
- status: 200,
77
- headers: { 'Content-Type' => 'application/json' },
78
- body: {something: 'A thing!', something_else: 'Woot!'}
79
- })
80
- # Use your service's client to make the request, rather than hand crafting a HTTP request,
81
- # so that you can be sure that the request that you expect to
82
- # be constructed is actually constructed by your client.
83
- # Do a quick sanity test to ensure client passes back the response properly.
84
- expect(SomeServiceClient.get_something).to eql({something: 'A thing!'})
131
+ subject { MyServiceProviderClient.new }
132
+
133
+ describe "get_something" do
134
+
135
+ before do
136
+ my_service_provider.given("something exists").
137
+ upon_receiving("a request for something").with(method: :get, path: '/something', query: '').
138
+ will_respond_with(
139
+ status: 200,
140
+ headers: {'Content-Type' => 'application/json'},
141
+ body: {name: 'A small something'} )
142
+ end
143
+
144
+ it "returns a Something" do
145
+ expect(subject.get_something).to eq(Something.new('A small something'))
146
+ end
147
+
85
148
  end
86
- end
87
149
 
150
+ end
88
151
  ```
89
152
 
153
+ #### 5. Run the specs
154
+
90
155
  Running the consumer spec will generate a pact file in the configured pact dir (spec/pacts by default).
91
156
  Logs will be output to the configured log dir that can be useful when diagnosing problems.
92
157
 
93
- To run your consumer app as a process during your test (eg for a Capybara test):
158
+ Of course, the above specs will fail because the client method is not implemented, so next, implement your client methods.
159
+
160
+ #### 6. Implement the client methods
94
161
 
95
162
  ```ruby
96
- Pact.configure do | config |
97
- config.consumer do
98
- name 'My Consumer'
99
- app my_consumer_rack_app
100
- port 4321
163
+ class MyServiceProviderClient
164
+ include HTTParty
165
+ base_uri 'http://my-service'
166
+
167
+ def get_something
168
+ name = JSON.parse(self.class.get("/something").body)['name']
169
+ Something.new(name)
101
170
  end
171
+ end
102
172
  ```
103
173
 
104
- ### Producer project
174
+ #### 7. Run the specs again.
105
175
 
106
- #### Configure your producer rack app
176
+ Green! You now have a pact file that can be used to verify your expectations of the provider project.
107
177
 
108
- ```ruby
178
+ Now, rinse and repeat for other likely status codes that may be returned. For example, consider how you want your client to respond to a:
179
+ * 404 (return null, or raise an error?)
180
+ * 500 (specifying that the response body should contain an error message, and ensuring that your client logs that error message will make your life much easier when things go wrong)
181
+ * 401/403 if there is authorisation.
109
182
 
110
- Pact.configure do | config |
111
- config.producer do
112
- name "My Producer"
113
- app { MyApp.new }
114
- end
115
- end
183
+ ### Service Provider project
116
184
 
117
- ```
185
+ #### 1. Create the skeleton API classes
118
186
 
119
- #### Set up the producer states
187
+ Create your API class using the framework of your choice (the Pact authors have a preference for [Webmachine][webmachine] and [Roar][roar]) - leave the methods unimplemented, we're doing Test First Develoment, remember?
120
188
 
121
- Having different producer states allows you to test the same request with different expected responses.
189
+ #### 2. Tell your provider that it needs to honour the pact file you made earlier
122
190
 
123
- For example, some code that creates the pact in a consumer project might look like this:
191
+ Require "pact/tasks" in your Rakefile.
124
192
 
125
193
  ```ruby
126
- my_service.
127
- given("a thing exists").
128
- upon_receiving("a request for a thing").
129
- with({method: 'get', path: '/thing'}).
130
- will_respond_with({status: 200, :body => {thing: "yay!"} })
131
-
132
- my_service.
133
- given("a thing does not exist").
134
- upon_receiving("a request for a thing").
135
- with({method: 'get', path: '/thing'}).
136
- will_respond_with({status: 404, :body => {error: "There is no thing :("} })
194
+ # In Rakefile
195
+ require 'pact/tasks'
137
196
  ```
138
197
 
139
- To define producer states that create the right data for "a thing exists" and "a thing does not exist", write the following in the producer project.
140
- Note that these states have been defined only for the 'My Consumer' consumer by using the Pact.with_consumer block.
198
+ Create a `pact_helper.rb` in your service provider project. The recommended place is `spec/service_consumers/pact_helper.rb`.
141
199
 
200
+ See [Verifying Pacts](https://github.com/realestate-com-au/pact/wiki/Verifying-pacts) and the [Provider](documentation/configuration.md#provider) section of the Configuration documentation for more information.
142
201
 
143
202
  ```ruby
144
- # The consumer name here must match the name of the consumer configured in your consumer project
145
- # for it to use these states.
203
+ # In specs/service_consumers/pact_helper.rb
146
204
 
147
- Pact.with_consumer 'My Consumer' do
148
- producer_state "a thing exists" do
149
- set_up do
150
- # Create a thing here using your factory of choice
151
- end
205
+ require 'pact/provider/rspec'
152
206
 
153
- tear_down do
154
- # Any tear down steps to clean up your code (or use RSpec.after(:each))
155
- end
156
- end
207
+ Pact.service_provider "My Service Provider" do
157
208
 
158
- producer_state "a thing does not exist" do
159
- set_up do
160
- # Well, probably not much to do here, but you get the picture.
161
- end
209
+ honours_pact_with 'My Service Consumer' do
210
+
211
+ # This example points to a local file, however, on a real project with a continuous
212
+ # integration box, you would use a [Pact Broker](https://github.com/bethesque/pact_broker) or publish your pacts as artifacts,
213
+ # and point the pact_uri to the pact published by the last successful build.
214
+
215
+ pact_uri '../path-to-your-consumer-project/specs/pacts/my_consumer-my_provider.json'
162
216
  end
163
217
  end
164
-
165
218
  ```
166
219
 
167
- If a state should be used for all consumers, the top level Pact.with_consumer can be skipped, and a global Pact.producer_state can be defined on its own.
220
+ #### 3. Run your failing specs
168
221
 
169
- #### Create a rake task to verify that the producer honours the pact
222
+ $ rake pact:verify
170
223
 
171
- You'll need to create one or more pact:verify:xxx tasks, that allow the currently checked out producer to be tested against other versions of its consumers - most importantly, head and production.
224
+ Congratulations! You now have a failing spec to develop against.
172
225
 
173
- Here is an example pact:verify:head task, pointing the the pact file for "some_consumer", found in the build artifacts of the latest successful build of "MY-CONSUMER" project.
226
+ At this stage, you'll want to be able to run your specs one at a time while you implement each feature. At the bottom of the failed pact:verify output you will see the commands to rerun each failed interaction individually. A command to run just one interaction will look like this:
174
227
 
175
- ```ruby
176
- Pact::VerificationTask.new(:head) do | pact |
177
- pact.uri 'http://our_build_server/MY-CONSUMER-BUILD/latestSuccessful/artifact/Pacts/some_consumer-this_producer.json',
178
- support_file: './spec/consumers/pact_helper'
179
- end
180
- ```
228
+ $ rake pact:verify PACT_DESCRIPTION="a request for something" PACT_PROVIDER_STATE="something exists"
181
229
 
182
- ```ruby
183
- # Ideally we'd like to be able to create a production task like this, but firewalls are making this tricky right now.
184
- Pact::VerificationTask.new(:production) do | pact |
185
- pact.uri 'http://our_prod_server/pacts/some_consumer-this_producer.json',
186
- support_file: './spec/consumers/pact_helper', consumer: 'some_consumer'
187
- end
188
- ```
230
+ #### 4. Implement enough to make your first interaction spec pass
231
+
232
+ Rinse and repeat.
233
+
234
+ #### 5. Keep going til you're green
235
+
236
+ Yay! Your provider now honours the pact it has with your consumer. You can now have confidence that your consumer and provider will play nicely together.
237
+
238
+ ### Using provider states
239
+
240
+ Each interaction in a pact is verified in isolation, with no context maintained from the previous interactions. So how do you test a request that requires data to already exist on the provider? Read about provider states [here](https://github.com/realestate-com-au/pact/wiki/Provider-states).
241
+
242
+
243
+ ## Configuration
244
+
245
+ See the [Configuration](/documentation/configuration.md) section of the documentation for options relating to thing like logging, diff formatting, and documentation generation.
246
+
247
+ ## Pact best practices
189
248
 
190
- The pact.uri may be a local file system path or a remote URL.
249
+ As in all things, there are good ways to implement Pacts, and there are not so good ways. Check out the [Best practices](https://github.com/realestate-com-au/pact/wiki/Best-practices) section of the documentation to make sure you're not Pacting it Wrong.
191
250
 
192
- The consumer is optional, and specifies which consumer namespace to use when looking up the producer states, if consumer namespaces have been used.
251
+ ## Docs
193
252
 
194
- The support_file should include the code that makes your rack app available for the rack testing framework, and should load all its dependencies (eg include spec_helper)
253
+ * [Example](example)
254
+ * [Configuration](documentation/configuration.md)
255
+ * [Terminology](https://github.com/realestate-com-au/pact/wiki/Terminology)
256
+ * [Provider States](https://github.com/realestate-com-au/pact/wiki/Provider-states)
257
+ * [Verifying pacts](https://github.com/realestate-com-au/pact/wiki/Verifying-pacts)
258
+ * [Sharing pacts between consumer and provider](https://github.com/realestate-com-au/pact/wiki/Sharing-pacts-between-consumer-and-provider)
259
+ * [Frequently asked questions](https://github.com/realestate-com-au/pact/wiki/FAQ)
260
+ * [Rarely asked questions](https://github.com/realestate-com-au/pact/wiki/RAQ)
261
+ * [Best practices](https://github.com/realestate-com-au/pact/wiki/Best-practices)
262
+ * [Troubleshooting](https://github.com/realestate-com-au/pact/wiki/Troubleshooting)
263
+ * [Testing with pact diagram](https://github.com/realestate-com-au/pact/wiki/Testing with pact.png)
195
264
 
196
- Multiple pact.uri may be defined in the same rake task if a producer has more than one consumer.
265
+ ## Related libraries
197
266
 
198
- #### Verify that the producer honours the pact
267
+ [Pact Provider Proxy](https://github.com/bethesque/pact-provider-proxy) - Verify a pact against a running server, allowing you to use pacts with a provider of any language.
199
268
 
200
- rake pact:verify:head
201
- rake pact:verify # will run all verify tasks
269
+ [Pact Broker](https://github.com/bethesque/pact_broker) - A pact repository. Provides endpoints to access published pacts, meaning you don't need to use messy CI URLs in your codebase. Enables cross testing of prod/head versions of your consumer and provider, allowing you to determine whether the head version of one is compatible with the production version of the other. Helps you to answer that ever so important question, "can I deploy without breaking all the things?"
202
270
 
271
+ [Pact Broker Client](https://github.com/bethesque/pact_broker-client) - Contains rake tasks for publishing pacts to the pact_broker.
203
272
 
204
- ### Running a standalone mock server
205
- A pact service can be run locally and is really useful for debugging purposes.
273
+ [Pact JVM](https://github.com/DiUS/pact-jvm) - A Pact implementation for the JVM (Java and Scala). It generates pact files that are compatible with the Ruby implementation.
206
274
 
207
- $ bundle exec pact service -p <port-num>
275
+ [Pact .NET](https://github.com/SEEK-Jobs/pact-net) - A Pact implementation for .NET.
208
276
 
209
- The service prints messages it recieves to stdout which can be really useful
210
- when diagnosing issues with pacts.
277
+ [Shokkenki](https://github.com/brentsnook/shokkenki) - Another Consumer Driven Contract gem written by one of Pact's original authors, Brent Snook. Shokkenki allows matchers to be composed using jsonpath expressions and allows auto-generation of mock response values based on regular expressions.
278
+
279
+ ## Links
280
+
281
+ [Simplifying microservices testing with pacts](http://dius.com.au/2014/05/19/simplifying-micro-service-testing-with-pacts/) - Ron Holshausen (one of the original pact authors)
282
+
283
+ [Pact specification](https://github.com/bethesque/pact-specification)
284
+
285
+ [Integrated tests are a scam](http://vimeo.com/80533536) - J.B. Rainsberger
286
+
287
+ [Consumer Driven Contracts](http://martinfowler.com/articles/consumerDrivenContracts.html) - Ian Robinson
288
+
289
+ [Integration Contract Tests](http://martinfowler.com/bliki/IntegrationContractTest.html) - Martin Fowler
211
290
 
212
291
  ## TODO
213
292
 
214
293
  Short term:
215
- - Rename ConsumerContract to ConsumerContract (Done)
216
- - Simplify set up for consumer (Done)
217
- - Move server spawning into to the "at" method (Done)
218
- - automatically register before and after hooks in consumer (Done)
219
- - Provide before and after hooks and a place to define the app for Pact configuration in producer (remove Rspc from interface of Pact setup) (Done)
220
- - Set_up for state
221
- - Tear_down for state
222
- - Before hook for all
223
- - After hook for all
224
- - Make producer state lookup try consumer defined state first, then fall back to global one (Done)
225
- - Put producer and consumer name into pact file (Done)
226
- - Remove consumer name from the rake task, as it should now be able to be determined from the pact file. (Done)
294
+ - Support hash of query params
227
295
 
228
296
  Long term:
297
+ - Provide more flexible matching (eg the keys should match, and the classes of the values should match, but the values of each key do not need to be equal). This is to make the pact verification less brittle.
298
+ - Add XML support
229
299
  - Decouple Rspec from Pact and make rspec-pact gem for easy integration
230
300
 
231
-
232
301
  ## Contributing
233
302
 
234
303
  1. Fork it
@@ -236,3 +305,9 @@ Long term:
236
305
  3. Commit your changes (`git commit -am 'Add some feature'`)
237
306
  4. Push to the branch (`git push origin my-new-feature`)
238
307
  5. Create new Pull Request
308
+
309
+ If you would like to implement pact in another language, please check out the [Pact specification](https://github.com/bethesque/pact-specification) and have a chat to one of us on the [pact-dev Google group](https://groups.google.com/forum/#!forum/pact-dev). The vision is to have a compatible pact implementation in all the commonly used languages, your help would be greatly appreciated!
310
+
311
+ [webmachine]: https://github.com/seancribbs/webmachine-ruby
312
+ [roar]: https://github.com/apotonick/roar
313
+
data/Rakefile CHANGED
@@ -1,33 +1,9 @@
1
- require 'bundler/gem_helper'
2
- module Bundler
3
- class GemHelper
4
- def install
5
- desc "Build #{name}-#{version}.gem into the pkg directory"
6
- task 'build' do
7
- build_gem
8
- end
9
-
10
- desc "Build and install #{name}-#{version}.gem into system gems"
11
- task 'install' do
12
- install_gem
13
- end
14
-
15
- GemHelper.instance = self
16
- end
17
- end
18
- end
19
- Bundler::GemHelper.install_tasks
1
+ require "bundler/gem_tasks"
20
2
  require 'rspec/core/rake_task'
21
- require 'geminabox-client'
22
3
 
23
- Dir.glob('lib/tasks/**/*.rake').each { |task| load task }
24
- Dir.glob('tasks/**/*.rake').each { |task| load task }
4
+ Dir.glob('./lib/tasks/**/*.rake').each { |task| load task }
5
+ Dir.glob('./tasks/**/*.rake').each { |task| load task }
25
6
  RSpec::Core::RakeTask.new(:spec)
26
7
 
27
- task :default => [:spec, 'pact:tests']
8
+ task :default => [:spec, 'pact:tests:all', :spec_with_active_support, 'pact:tests:all:with_active_support']
28
9
 
29
- desc "Release to REA gems host"
30
- task :publish => :build do
31
- gem_file = "pkg/pact-#{Pact::VERSION}.gem"
32
- Geminabox::Client.new('http://rea-rubygems').upload(gem_file)
33
- end
data/bin/pact CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'pact/app'
4
- Pact::App.start
3
+ require 'pact/cli'
4
+ Pact::CLI.start
data/config.ru ADDED
@@ -0,0 +1,3 @@
1
+ require './spec/support/app_for_config_ru'
2
+
3
+ run AppForConfigRu
@@ -0,0 +1,13 @@
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](https://github.com/realestate-com-au/pact/wiki/Terminology)
5
+ * [Configuration](configuration.md)
6
+ * [Provider States](https://github.com/realestate-com-au/pact/wiki/Provider-states)
7
+ * [Verifying pacts](https://github.com/realestate-com-au/pact/wiki/Verifying-pacts)
8
+ * [Frequently asked questions](https://github.com/realestate-com-au/pact/wiki/FAQ)
9
+ * [Rarely asked questions](https://github.com/realestate-com-au/pact/wiki/RAQ)
10
+ * [Best practices](https://github.com/realestate-com-au/pact/wiki/Best-practices)
11
+ * [Troubleshooting](https://github.com/realestate-com-au/pact/wiki/Troubleshooting)
12
+ * [Testing with pact diagram](https://github.com/realestate-com-au/pact/wiki/Testing with pact.png)
13
+ * [Development workflow](https://github.com/realestate-com-au/pact/wiki/Development-workflow)