Skip to content
This repository has been archived by the owner on Dec 5, 2019. It is now read-only.

Generation hints #135

Merged
merged 8 commits into from
Sep 26, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
69 changes: 69 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
Just require pacto to add it to your project.

```rb
require 'pacto'
```

Pacto will disable live connections, so you will get an error if
your code unexpectedly calls an service that was not stubbed. If you
want to re-enable connections, run `WebMock.allow_net_connect!`

```rb
WebMock.allow_net_connect!
```

Pacto can be configured via a block:

```rb
Pacto.configure do |c|
```

Path for loading/storing contracts.

```rb
c.contracts_path = 'contracts'
```

If the request matching should be strict (especially regarding HTTP Headers).

```rb
c.strict_matchers = true
```

You can set the Ruby Logger used by Pacto.

```rb
c.logger = Pacto::Logger::SimpleLogger.instance
```

(Deprecated) You can specify a callback for post-processing responses. Note that only one hook
can be active, and specifying your own will disable ERB post-processing.

```rb
c.register_hook do |_contracts, request, _response|
puts "Received #{request}"
end
```

Options to pass to the [json-schema-generator](https://github.com/maxlinc/json-schema-generator) while generating contracts.

```rb
c.generator_options = { schema_version: 'draft3' }
end
```

You can also do inline configuration. This example tells the json-schema-generator to store default values in the schema.

```rb
Pacto.configuration.generator_options = { defaults: true }
```

If you're using Pacto's rspec matchers you might want to configure a reset between each scenario

```rb
require 'pacto/rspec'
RSpec.configure do |c|
c.after(:each) { Pacto.clear! }
end
```

39 changes: 39 additions & 0 deletions docs/cops.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

```rb
require 'pacto'
Pacto.configure do |c|
c.contracts_path = 'contracts'
end
Pacto.validate!
```

You can create a custom cop that investigates the request/response and sees if it complies with a
contract. The cop should return a list of citations if it finds any problems.

```rb
class MyCustomCop
def investigate(_request, _response, contract)
citations = []
citations << 'Contract must have a request schema' if contract.request.schema.empty?
citations << 'Contract must have a response schema' if contract.response.schema.empty?
citations
end
end

Pacto::Cops.active_cops << MyCustomCop.new

contracts = Pacto.load_contracts('contracts', 'http://localhost:5000')
contracts.stub_providers
puts contracts.simulate_consumers
```

Or you can completely replace the default set of validators

```rb
Pacto::Cops.registered_cops.clear
Pacto::Cops.register_cop Pacto::Cops::ResponseBodyCop

contracts = Pacto.load_contracts('contracts', 'http://localhost:5000')
puts contracts.simulate_consumers
```

65 changes: 65 additions & 0 deletions docs/forensics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
Pacto has a few RSpec matchers to help you ensure a **consumer** and **producer** are
interacting properly. First, let's setup the rspec suite.

```rb
require 'rspec/autorun' # Not generally needed
require 'pacto/rspec'
WebMock.allow_net_connect!
Pacto.validate!
Pacto.load_contracts('contracts', 'http://localhost:5000').stub_providers
```

It's usually a good idea to reset Pacto between each scenario. `Pacto.reset` just clears the
data and metrics about which services were called. `Pacto.clear!` also resets all configuration
and plugins.

```rb
RSpec.configure do |c|
c.after(:each) { Pacto.reset }
end
```

Pacto provides some RSpec matchers related to contract testing, like making sure
Pacto didn't received any unrecognized requests (`have_unmatched_requests`) and that
the HTTP requests matched up with the terms of the contract (`have_failed_investigations`).

```rb
describe Faraday do
let(:connection) { described_class.new(url: 'http://localhost:5000') }

it 'passes contract tests' do
connection.get '/api/ping'
expect(Pacto).to_not have_failed_investigations
expect(Pacto).to_not have_unmatched_requests
end
end
```

There are also some matchers for collaboration testing, so you can make sure each scenario is
calling the expected services and sending the right type of data.

```rb
describe Faraday do
let(:connection) { described_class.new(url: 'http://localhost:5000') }
before(:each) do
connection.get '/api/ping'

connection.post do |req|
req.url '/api/echo'
req.headers['Content-Type'] = 'application/json'
req.body = '{"foo": "bar"}'
end
end

it 'calls the ping service' do
expect(Pacto).to have_validated(:get, 'http://localhost:5000/api/ping').against_contract('Ping')
end

it 'sends data to the echo service' do
expect(Pacto).to have_investigated('Ping').with_response(body: hash_including('ping' => 'pong - from the example!'))
expect(Pacto).to have_investigated('Echo').with_request(body: hash_including('foo' => 'bar'))
expect(Pacto).to have_investigated('Echo').with_response(body: /foo.*bar/)
end
end
```

65 changes: 65 additions & 0 deletions docs/generation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
Some generation related [configuration](configuration.rb).

```rb
require 'pacto'
WebMock.allow_net_connect!
Pacto.configure do |c|
c.contracts_path = 'contracts'
end
WebMock.allow_net_connect!
```

Once we call `Pacto.generate!`, Pacto will record contracts for all requests it detects.

```rb
Pacto.generate!
```

Now, if we run any code that makes an HTTP call (using an
[HTTP library supported by WebMock](https://github.com/bblimke/webmock#supported-http-libraries))
then Pacto will generate a Contract based on the HTTP request/response.

This code snippet will generate a Contract and save it to `contracts/samples/contracts/localhost/api/ping.json`.

```rb
require 'faraday'
conn = Faraday.new(url: 'http://localhost:5000')
response = conn.get '/api/ping'
```

We're getting back real data from GitHub, so this should be the actual file encoding.

```rb
puts response.body
```

The generated contract will contain expectations based on the request/response we observed,
including a best-guess at an appropriate json-schema. Our heuristics certainly aren't foolproof,
so you might want to customize schema!
Here's another sample that sends a post request.

```rb
conn.post do |req|
req.url '/api/echo'
req.headers['Content-Type'] = 'application/json'
req.body = '{"red fish": "blue fish"}'
end
```

You can provide hints to Pacto to help it generate contracts. For example, Pacto doesn't have
a good way to know a good name and correct URI template for the service. That means that Pacto
will not know if two similar requests are for the same service or two different services, and
will be forced to give names based on the URI that are not good display names.
The hint below tells Pacto that requests to http://localhost:5000/album/1/cover and http://localhost:5000/album/2/cover
are both going to the same service, which is known as "Get Album Cover". This hint will cause Pacto to
generate a Contract for "Get Album Cover" and save it to `contracts/get_album_cover.json`, rather than two
contracts that are stored at `contracts/localhost/album/1/cover.json` and `contracts/localhost/album/2/cover.json`.

```rb
Pacto::Generator.configure do |c|
c.hint 'Get Album Cover', http_method: :get, host: 'http://localhost:5000', path: '/api/album/{id}/cover'
end
conn.get '/api/album/1/cover'
conn.get '/api/album/2/cover'
```

10 changes: 10 additions & 0 deletions docs/rake_tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Rake tasks
## This is a test!
[That](www.google.com) markdown works

```sh
bundle exec rake pacto:meta_validate['contracts']

bundle exec rake pacto:validate['http://localhost:5000','contracts']
```

Empty file added docs/rspec.md
Empty file.
133 changes: 133 additions & 0 deletions docs/samples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Overview
Welcome to the Pacto usage samples!
This document gives a quick overview of the main features.

You can browse the Table of Contents (upper right corner) to view additional samples.

In addition to this document, here are some highlighted samples:
<ul>
<li><a href="configuration">Configuration</a>: Shows all available configuration options</li>
<li><a href="generation">Generation</a>: More details on generation</li>
<li><a href="rspec">RSpec</a>: More samples for RSpec expectations</li>
</ul>
You can also find other samples using the Table of Content (upper right corner), including sample contracts.
# Getting started
Once you've installed the Pacto gem, you just require it. If you want, you can also require the Pacto rspec expectations.

```rb
require 'pacto'
require 'pacto/rspec'
```

Pacto will disable live connections, so you will get an error if
your code unexpectedly calls an service that was not stubbed. If you
want to re-enable connections, run `WebMock.allow_net_connect!`

```rb
WebMock.allow_net_connect!
```

Pacto can be configured via a block. The `contracts_path` option tells Pacto where it should load or save contracts. See the [Configuration](configuration.html) for all the available options.

```rb
Pacto.configure do |c|
c.contracts_path = 'contracts'
end
```

# Generating a Contract
Calling `Pacto.generate!` enables contract generation.
Pacto.generate!
Now, if we run any code that makes an HTTP call (using an
[HTTP library supported by WebMock](https://github.com/bblimke/webmock#supported-http-libraries))
then Pacto will generate a Contract based on the HTTP request/response.

We're using the sample APIs in the sample_apis directory.

```rb
require 'faraday'
conn = Faraday.new(url: 'http://localhost:5000')
response = conn.get '/api/ping'
```

This is the real request, so you should see {"ping":"pong"}

```rb
puts response.body
```

# Testing providers by simulating consumers
The generated contract will contain expectations based on the request/response we observed,
including a best-guess at an appropriate json-schema. Our heuristics certainly aren't foolproof,
so you might want to modify the output!
We can load the contract and validate it, by sending a new request and making sure
the response matches the JSON schema. Obviously it will pass since we just recorded it,
but if the service has made a change, or if you alter the contract with new expectations,
then you will see a contract investigation message.

```rb
contracts = Pacto.load_contracts('contracts', 'http://localhost:5000')
contracts.simulate_consumers
```

# Stubbing providers for consumer testing
We can also use Pacto to stub the service based on the contract.

```rb
contracts.stub_providers
```

The stubbed data won't be very realistic, the default behavior is to return the simplest data
that complies with the schema. That basically means that you'll have "bar" for every string.

```rb
response = conn.get '/api/ping'
```

You're now getting stubbed data. You should see {"ping":"bar"} unless you recorded with
the `defaults` option enabled, in which case you will still seee {"ping":"pong"}.

```rb
puts response.body
```

# Collaboration tests with RSpec
Pacto comes with rspec matchers

```rb
require 'pacto/rspec'
```

It's probably a good idea to reset Pacto between each rspec scenario

```rb
RSpec.configure do |c|
c.after(:each) { Pacto.clear! }
end
```

Load your contracts, and stub them if you'd like.

```rb
Pacto.load_contracts('contracts', 'http://localhost:5000').stub_providers
```

You can turn on investigation mode so Pacto will detect and validate HTTP requests.

```rb
Pacto.validate!

describe 'my_code' do
it 'calls a service' do
conn = Faraday.new(url: 'http://localhost:5000')
response = conn.get '/api/ping'
```

The have_validated matcher makes sure that Pacto received and successfully validated a request

```rb
expect(Pacto).to have_validated(:get, 'http://localhost:5000/api/ping')
end
end
```