diff --git a/.istanbul.yml b/.istanbul.yml index f80b97fdb..283c9932e 100644 --- a/.istanbul.yml +++ b/.istanbul.yml @@ -8,8 +8,8 @@ check: functions: 80 excludes: [] each: - statements: 80 - lines: 80 + statements: 60 + lines: 60 branches: 80 functions: 80 excludes: [] diff --git a/README.md b/README.md index fd77b8487..257adfc02 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![Issue Count](https://codeclimate.com/github/pact-foundation/pact-js/badges/issue_count.svg)](https://codeclimate.com/github/pact-foundation/pact-js) [![npm](https://img.shields.io/github/license/pact-foundation/pact-js.svg?maxAge=2592000)](https://github.com/pact-foundation/pact-js/blob/master/LICENSE) -Implementation of the consumer driven contract library [pact](https://github.com/pact-foundation/pact-specification) for Javascript. +Implementation of the consumer driven contract library [Pact](https://github.com/pact-foundation/pact-specification) for Javascript. From the [Pact website](http://docs.pact.io/): @@ -31,68 +31,67 @@ It's easy, simply run the below: npm install --save-dev pact ``` -### Examples - -* [Complete Example (Node env)](https://github.com/pact-foundation/pact-js/tree/master/examples/e2e) -* [Pact with Jest (Node env)](https://github.com/pact-foundation/pact-js/tree/master/examples/jest) -* [Pact with Mocha](https://github.com/pact-foundation/pact-js/tree/master/examples/mocha) -* [Pact with Karma + Jasmine](https://github.com/pact-foundation/pact-js/tree/master/karma/jasmine) -* [Pact with Karma + Mocha](https://github.com/pact-foundation/pact-js/tree/master/karma/mocha) - -#### Note on Jest -Jest uses JSDOM under the hood which may cause issues with libraries making HTTP request. See [this issue](https://github.com/pact-foundation/pact-js/issues/10) for background, -and the Jest [example](https://github.com/pact-foundation/pact-js/blob/master/examples/jest/package.json#L10-L12) for a working solution. +## Using Pact JS -### Using Pact JS - -#### Consumer Side Testing +### Using Mocha? -The library provides a Verifier Service, Matchers and an API Interceptor: +Check out [Pact JS Mocha](https://github.com/pact-foundation/pact-js-mocha). ->**Verifier** Sets up a test double (Verifier Provider API) with expected interactions. It's also responsible for generating Pact files. -> ->**Matchers** are functions you can use to increase the expressiveness of your tests, and reduce brittle test cases. See the [matching](http://docs.pact.io/documentation/matching.html) docs for more information. -> ->**Interceptor** is a utility that can be used to intercept requests to the Provider, where it's not simple for you to change your API endpoint. +### Consumer Side Testing -To use the library on your tests, do as you would normally with any other dependency: +To use the library on your tests, add the pact dependency: ```javascript -var Pact = require('pact') -var matchers = Pact.Matchers -matchers.term() -matchers.somethingLike() -matchers.eachLike() +let Pact = require('pact') ``` -Then to write a test that will generate a Pact file, here's an example below - it uses [Mocha](https://mochajs.org). There's a bit going on in there as we are spinning up the Pact Verifier Service Provider to mock a real server on the provider server. This is needed because that's where we will record our interactions. +The `Pact` interface provides the following high-level APIs, they are listed in the order in which they typically get called in the lifecycle of testing a consumer: -More questions about what's involved in Pact? [Read more about it](http://docs.pact.io/documentation/how_does_pact_work.html). +#### API +|API |Options |Returns|Description | +|-----------------------|------------|------------------------------------------------------|---| +|`pact(options)` |See [Pact Node documentation](https://github.com/pact-foundation/pact-node#create-pact-mock-server) for options |`Object` |Creates a Mock Server test double of your Provider API. If you need multiple Providers for a scenario, you can create as many as these as you need. | +|`setup()` |n/a |`Promise`|Start the Mock Server | +|`addInteraction()` |`Object` |`Promise`|Register an expectation on the Mock Server, which must be called by your test case(s). You can add multiple interactions per server. These will be validated and written to a pact if successful. +|`verify()` |n/a |`Promise`|Verifies that all interactions specified | +|`finalize()` |n/a |`Promise`|Records the interactions registered to the Mock Server into the pact file and shuts it down. | +|`removeInteractions` |n/a |`Promise`|In some cases you might want to clear out the expectations of the Mock Service, call this to clear out any expectations for the next test run. _NOTE_: `verify()` will implicitly call this. | -Check the `examples` folder for examples with Karma Jasmine / Mocha. The example below is taken from the [integration spec](https://github.com/pact-foundation/pact-js/blob/master/test/dsl/integration.spec.js). +#### Example +The first step is to create a test for your API Consumer. The example below uses [Mocha](https://mochajs.org), and demonstrates the basic approach: + +1. Create the Pact object +1. Start the Mock Provider that will stand in for your actual Provider +1. Add the interactions you expect your consumer code to make when executing the tests +1. Write your tests - the important thing here is that you test the outbound _collaborating_ function which calls the Provider, and not just issue raw http requests to the Provider. This ensures you are testing your actual running code, just like you would in any other unit test, and that the tests will always remain up to date with what your consumer is doing. +1. Validate the expected interactions were made between your consumer and the Mock Service +1. Generate the pact(s) + +Check out the `examples` folder for examples with Karma Jasmine, Mocha and Jest. The example below is taken from the [integration spec](https://github.com/pact-foundation/pact-js/blob/master/test/dsl/integration.spec.js). ```javascript -var path = require('path') -var chai = require('chai') -var Pact = require('pact') -var request = require ('superagent') -var chaiAsPromised = require('chai-as-promised') -var wrapper = require('@pact-foundation/pact-node') +let path = require('path') +let chai = require('chai') +let pact = require('pact') +let request = require ('superagent') +let chaiAsPromised = require('chai-as-promised') -var expect = chai.expect +let expect = chai.expect chai.use(chaiAsPromised); describe('Pact', () => { - // when using the wrapper, you will need to tell it where to store the logs - // make sure you the folders created before hand - const mockServer = wrapper.createServer({ - port: 1234, - log: path.resolve(process.cwd(), 'logs', 'mockserver-integration.log'), + // (1) Create the Pact object to represent your provider + const provider = pact({ + consumer: 'TodoApp', + provider: 'TodoService',, + port: MOCK_SERVER_PORT, + log: path.resolve(process.cwd(), 'logs', 'pact.log'), dir: path.resolve(process.cwd(), 'pacts'), + logLevel: 'INFO', spec: 2 - }) + }); // this is the response you expect from your Provider const EXPECTED_BODY = [{ @@ -107,84 +106,71 @@ describe('Pact', () => { ] }] - var provider - - after(() => { - wrapper.removeAllServers() - }); - - beforeEach((done) => { - mockServer.start().then(() => { - // in order to use the Verifier, simply pass an object like below - // it should contain the names of the consumer and provider in normal language - // you can also use a different port if you have multiple providers - provider = Pact({ consumer: 'My Consumer', provider: 'My Provider', port: 1234 }) - done() - }) - }) - - afterEach((done) => { - mockServer.delete().then(() => { - done() - }) - }) - - context('with a single request', () => { - describe('successfully writes Pact file', () => { - - // add interactions, as many as needed - beforeEach((done) => { - provider.addInteraction({ - state: 'i have a list of projects', - uponReceiving: 'a request for projects', - withRequest: { - method: 'GET', - path: '/projects', - headers: { 'Accept': 'application/json' } - }, - willRespondWith: { - status: 200, - headers: { 'Content-Type': 'application/json' }, - body: EXPECTED_BODY - } - }).then(() => done()) - }) - - // once test is run, write pact and remove interactions - afterEach((done) => { - provider.finalize().then(() => done()) + context('when there are a list of projects', () => { + describe('and there is a valid user session', () => { + + before((done) => { + // (2) Start the mock server + provider.setup() + // (3) add interactions to the Mock Server, as many as required + .then(() => { + provider.addInteraction({ + state: 'i have a list of projects', + uponReceiving: 'a request for projects', + withRequest: { + method: 'GET', + path: '/projects', + headers: { 'Accept': 'application/json' } + }, + willRespondWith: { + status: 200, + headers: { 'Content-Type': 'application/json' }, + body: EXPECTED_BODY + } + }) + }) + .then(() => done()) + }) + + // (4) write your test(s) + it('should generate a list of TODOs for the main screen', (done) => { + const todoApp = new TodoApp(); + const projects = todoApp.getProjects() // <- this method would make the remote http call + expect(projects).to.eventually.be.a('array') + expect(projects).to.eventually.have.deep.property('projects[0].id', 1).notify(done) }) - // and this is how the verification process invokes your request - // and writes the Pact file if all is well, returning you the data of the request - // so you can do your assertions - it('successfully verifies', (done) => { - const verificationPromise = request - .get('http://localhost:1234/projects') - .set({ 'Accept': 'application/json' }) - .then(provider.verify) - expect(verificationPromise).to.eventually.eql(JSON.stringify(EXPECTED_BODY)).notify(done) + // (5) validate the interactions occurred, this will throw an error if it fails telling you what went wrong + it('creates a contract between the TodoApp and TodoService', () => { + return pact.verify() }) }) - }) + }); + + // (6) write the pact file for this consumer-provider pair, + // and shutdown the associated mock server. + // You should do this only _once_ per Provider you are testing. + after(() => { + provider.finalize() + }); }) ``` #### Provider API Testing -Once you have created Pacts for your Consumer, you need to validate those Pacts against your Provider. +Once you have created Pacts for your Consumer, you need to validate those Pacts against your Provider. The Verifier object provides the following API for you to do so: -First, install [Pact Node](https://github.com/pact-foundation/pact-node): +|API |Options |Returns|Description | +|-----------------------|:------------:|----------------------------------------------|----| +|`verifyProvider()` |n/a |`Promise`|Start the Mock Server | -``` -npm install @pact-foundation/pact-node --save -``` - -Then run the Provider side verification step: +1. Start your local Provider service. +1. Optionally, instrument your API with ability to configure [provider states](https://github.com/pact-foundation/pact-provider-verifier/) +1. Then run the Provider side verification step ```js -var pact = require('@pact-foundation/pact-node'); -var opts = { +const verifier = require('pact').Verifier; +let opts = { providerBaseUrl: , // Running API provider host endpoint. Required. pactUrls: , // Array of local Pact file paths or Pact Broker URLs (http based). Required. providerStatesUrl: , // URL to fetch the provider states for the given provider API. Optional. @@ -193,7 +179,7 @@ var opts = { pactBrokerPassword: , // Password for Pact Broker basic authentication. Optional }; -pact.verifyPacts(opts)).then(function () { +verifier.verifyProvider(opts)).then(function () { // do something }); ``` @@ -202,11 +188,11 @@ That's it! Read more about [Verifying Pacts](http://docs.pact.io/documentation/v ### Publishing Pacts to a Broker -Sharing is caring - to simplify sharing Pacts between Consumers and Providers, checkout [sharing pacts](http://docs.pact.io/documentation/sharings_pacts.html). +Sharing is caring - to simplify sharing Pacts between Consumers and Providers, checkout [sharing pacts](http://docs.pact.io/documentation/sharings_pacts.html) using the [Pact Broker](https://github.com/bethesque/pact_broker). ```js -var pact = require('@pact-foundation/pact-node'); -var opts = { +let pact = require('@pact-foundation/pact-node'); +let opts = { pactUrls: , // Array of local Pact files or directories containing them. Required. pactBroker: , // URL to fetch the provider states for the given provider API. Optional. pactBrokerUsername: , // Username for Pact Broker basic authentication. Optional @@ -219,9 +205,17 @@ pact.publishPacts(opts)).then(function () { }); ``` -### Using Mocha? +### Examples -Check out [Pact JS Mocha](https://github.com/pact-foundation/pact-js-mocha). +* [Complete Example (Node env)](https://github.com/pact-foundation/pact-js/tree/master/examples/e2e) +* [Pact with Jest (Node env)](https://github.com/pact-foundation/pact-js/tree/master/examples/jest) +* [Pact with Mocha](https://github.com/pact-foundation/pact-js/tree/master/examples/mocha) +* [Pact with Karma + Jasmine](https://github.com/pact-foundation/pact-js/tree/master/karma/jasmine) +* [Pact with Karma + Mocha](https://github.com/pact-foundation/pact-js/tree/master/karma/mocha) + +#### Note on Jest +Jest uses JSDOM under the hood which may cause issues with libraries making HTTP request. See [this issue](https://github.com/pact-foundation/pact-js/issues/10) for background, +and the Jest [example](https://github.com/pact-foundation/pact-js/blob/master/examples/jest/package.json#L10-L12) for a working solution. ## Contributing 1. Fork it diff --git a/config/webpack.web.config.js b/config/webpack.web.config.js index ccf60b35a..be824b1ce 100644 --- a/config/webpack.web.config.js +++ b/config/webpack.web.config.js @@ -6,7 +6,7 @@ var DIST = path.resolve(__dirname, '../dist'); var APP = path.resolve(__dirname, '../src'); module.exports = { - entry: path.resolve(APP, 'pact.js'), + entry: path.resolve(APP, 'pact-karma.js'), output: { path: DIST, library: 'Pact', diff --git a/docs/index.html b/docs/index.html index 2059d5049..3b78d93f2 100644 --- a/docs/index.html +++ b/docs/index.html @@ -42,6 +42,12 @@

Pact

.create +
  • + .Verifier +
  • +
  • @@ -108,6 +114,16 @@

    Pact

  • +
  • + setup + + + +
  • + +
  • @@ -147,6 +163,16 @@

    Pact

  • +
  • + ProviderVerifier + + + +
  • + +
  • @@ -272,7 +298,7 @@

    Pact

    -
    + src/pact.js @@ -405,6 +431,51 @@

    + + + + + + + + + +
    +
    +
    + + Verifier +
    +
    + + + + + + + + + + +
    + + +
    +

    + ProviderVerifier +

    + + + src/dsl/verifier.js + + +
    + + +

    Provider Verifier service

    + + +
    ProviderVerifier
    + + + + + + + + + + + + + + + + + + + + @@ -1101,15 +1265,16 @@

    MockService

    - + src/dsl/mockService.js
    -

    A Mock Service is the interaction mechanism through which pacts get written and verified. -This should be transparent to the end user.

    +

    Mock Service is the HTTP interface to setup the Pact Mock Service. +See https://github.com/bethesque/pact-mock_service and +https://gist.github.com/bethesque/9d81f21d6f77650811f4.

    MockService
    @@ -1465,7 +1630,7 @@

    Interaction

    - + src/dsl/interaction.js diff --git a/examples/e2e/.gitignore b/examples/e2e/.gitignore new file mode 100644 index 000000000..8000dd9db --- /dev/null +++ b/examples/e2e/.gitignore @@ -0,0 +1 @@ +.vagrant diff --git a/examples/e2e/consumer.js b/examples/e2e/consumer.js index c6c050af4..d5e8b5c04 100644 --- a/examples/e2e/consumer.js +++ b/examples/e2e/consumer.js @@ -1,7 +1,7 @@ -const express = require('express'); -const request = require('superagent'); -const server = express(); -const API_HOST = process.env.API_HOST || 'http://localhost:8081'; +const express = require('express') +const request = require('superagent') +const server = express() +const API_HOST = process.env.API_HOST || 'http://localhost:8081' // Fetch animals who are currently 'available' from the // Animal Service @@ -9,16 +9,16 @@ const availableAnimals = () => { return request .get(`${API_HOST}/animals/available`) .then(res => res.body, - () => []); -}; + () => []) +} // Find animals by their ID from the Animal Service const getAnimalById = (id) => { return request .get(`${API_HOST}/animals/${id}`) .then(res => res.body, - () => null); -}; + () => null) +} // Suggestions function: // Given availability and sex etc. find available suitors, @@ -28,55 +28,55 @@ const suggestion = mate => { ((candidate, animal) => candidate.id !== animal.id), ((candidate, animal) => candidate.gender !== animal.gender), ((candidate, animal) => candidate.animal === animal.animal) - ]; + ] const weights = [ ((candidate, animal) => Math.abs(candidate.age - animal.age)) - ]; + ] return availableAnimals().then(available => { - const eligible = available.filter(a => !predicates.map(p => p(a, mate)).includes(false)); + const eligible = available.filter(a => !predicates.map(p => p(a, mate)).includes(false)) return { suggestions: eligible.map(candidate => { const score = weights.reduce((acc, weight) => { - return acc - weight(candidate, mate); - }, 100); + return acc - weight(candidate, mate) + }, 100) return { score, 'animal': candidate - }; + } }) - }; - }); -}; + } + }) +} // Suggestions API server.get('/suggestions/:animalId', (req, res) => { if (!req.params.animalId) { - res.writeHead(400); - res.end(); + res.writeHead(400) + res.end() } request(`${API_HOST}/animals/${req.params.animalId}`, (err, r) => { if (!err && r.statusCode === 200) { suggestion(r.body).then(suggestions => { - res.json(suggestions); - }); + res.json(suggestions) + }) } else if (r && r.statusCode === 404) { - res.writeHead(404); - res.end(); + res.writeHead(404) + res.end() } else { - res.writeHead(500); - res.end(); + res.writeHead(500) + res.end() } - }); -}); + }) +}) module.exports = { server, availableAnimals, suggestion, getAnimalById -}; +} diff --git a/examples/e2e/consumerService.js b/examples/e2e/consumerService.js index ebea601a8..b80ae9906 100644 --- a/examples/e2e/consumerService.js +++ b/examples/e2e/consumerService.js @@ -1,5 +1,5 @@ -const { server } = require('./consumer.js'); +const { server } = require('./consumer.js') server.listen(8080, () => { - console.log('Animal Matching Service listening on http://localhots:8080'); -}); + console.log('Animal Matching Service listening on http://localhots:8080') +}) diff --git a/examples/e2e/package.json b/examples/e2e/package.json index a6f04712b..0fa06d648 100644 --- a/examples/e2e/package.json +++ b/examples/e2e/package.json @@ -14,6 +14,7 @@ "license": "MIT", "devDependencies": { "@pact-foundation/pact-node": "^4.6.0", + "cli-color": "^1.1.0", "concurrently": "^3.1.0", "eslint": "^3.13.1", "eslint-config-google": "^0.7.1", diff --git a/examples/e2e/provider.js b/examples/e2e/provider.js index a4b5601b1..ee7f1f2a1 100644 --- a/examples/e2e/provider.js +++ b/examples/e2e/provider.js @@ -1,78 +1,80 @@ -const express = require('express'); -const bodyParser = require('body-parser'); -const Repository = require('./repository'); +const express = require('express') +const cors = require('cors') +const bodyParser = require('body-parser') +const Repository = require('./repository') -const server = express(); -server.use(bodyParser.json()); +const server = express() +server.use(cors()) +server.use(bodyParser.json()) server.use(bodyParser.urlencoded({ extended: true -})); +})) server.use((req, res, next) => { - res.header('Content-Type', 'application/json; charset=utf-8'); - next(); -}); + res.header('Content-Type', 'application/json charset=utf-8') + next() +}) -const animalRepository = new Repository(); +const animalRepository = new Repository() // Load default data into a repository const importData = () => { - const data = require('./data/animalData.json'); + const data = require('./data/animalData.json') data.reduce((a, v) => { - v.id = a + 1; - animalRepository.insert(v); - return a + 1; - }, 0); -}; + v.id = a + 1 + animalRepository.insert(v) + return a + 1 + }, 0) +} // List all animals with 'available' eligibility const availableAnimals = () => { return animalRepository.fetchAll().filter(a => { - return a.eligibility.available; - }); -}; + return a.eligibility.available + }) +} // Get all animals server.get('/animals', (req, res) => { - res.json(animalRepository.fetchAll()); -}); + res.json(animalRepository.fetchAll()) +}) // Get all available animals server.get('/animals/available', (req, res) => { - res.json(availableAnimals()); -}); + res.json(availableAnimals()) +}) // Find an animal by ID server.get('/animals/:id', (req, res) => { - const response = animalRepository.getById(req.params.id); + const response = animalRepository.getById(req.params.id) if (response) { - res.end(JSON.stringify(response)); + res.end(JSON.stringify(response)) } else { - res.writeHead(404); - res.end(); + res.writeHead(404) + res.end() } -}); +}) // Register a new Animal for the service server.post('/animals', (req, res) => { - const animal = req.body; + const animal = req.body // Really basic validation if (!animal || !animal.first_name) { - res.writeHead(400); - res.end(); + res.writeHead(400) + res.end() - return; + return } - animal.id = animalRepository.fetchAll().length; - animalRepository.insert(animal); + animal.id = animalRepository.fetchAll().length + animalRepository.insert(animal) - res.writeHead(200); - res.end(); -}); + res.writeHead(200) + res.end() +}) module.exports = { server, importData, animalRepository -}; +} diff --git a/examples/e2e/providerService.js b/examples/e2e/providerService.js index 902f20378..0e1c82dc0 100644 --- a/examples/e2e/providerService.js +++ b/examples/e2e/providerService.js @@ -1,6 +1,6 @@ -const { server, importData } = require('./provider.js'); -importData(); +const { server, importData } = require('./provider.js') +importData() server.listen(8081, () => { - console.log('Animal Profile Service listening on http://localhost:8081'); -}); + console.log('Animal Profile Service listening on http://localhost:8081') +}) diff --git a/examples/e2e/repository.js b/examples/e2e/repository.js index 3bfb51254..04fe65b8e 100644 --- a/examples/e2e/repository.js +++ b/examples/e2e/repository.js @@ -1,24 +1,24 @@ // Simple object repository class Repository { constructor() { - this.entities = []; + this.entities = [] } fetchAll() { - return this.entities; + return this.entities } getById(id) { - return this.entities.find((entity) => id == entity.id); + return this.entities.find((entity) => id == entity.id) } insert(entity) { - this.entities.push(entity); + this.entities.push(entity) } clear() { - this.entities = []; + this.entities = [] } } -module.exports = Repository; +module.exports = Repository diff --git a/examples/e2e/test/consumer.spec.js b/examples/e2e/test/consumer.spec.js index 03fe39287..d609536fa 100644 --- a/examples/e2e/test/consumer.spec.js +++ b/examples/e2e/test/consumer.spec.js @@ -1,30 +1,28 @@ -const path = require('path'); -const chai = require('chai'); -const chaiAsPromised = require('chai-as-promised'); -const expect = chai.expect; -const pact = require('pact'); -const mockservice = require('@pact-foundation/pact-node'); -const MOCK_SERVER_PORT = 1234; -const LOG_LEVEL = process.env.LOG_LEVEL || 'WARN'; +const path = require('path') +const chai = require('chai') +const chaiAsPromised = require('chai-as-promised') +const expect = chai.expect +const pact = require('../../../src/pact.js') +const MOCK_SERVER_PORT = 1234 +const LOG_LEVEL = process.env.LOG_LEVEL || 'WARN' -chai.use(chaiAsPromised); +chai.use(chaiAsPromised) describe('Pact', () => { - let provider; - - // Alias flexible matchers for simplicity - const term = pact.Matchers.term; - const like = pact.Matchers.somethingLike; - const eachLike = pact.Matchers.eachLike; - - // Configure mock server - const mockServer = mockservice.createServer({ + const provider = pact({ + consumer: 'Matching Service', + provider: 'Animal Profile Service', port: MOCK_SERVER_PORT, log: path.resolve(process.cwd(), 'logs', 'mockserver-integration.log'), dir: path.resolve(process.cwd(), 'pacts'), + logLevel: LOG_LEVEL, spec: 2 - }); - mockservice.logLevel(LOG_LEVEL); + }) + + // Alias flexible matchers for simplicity + const term = pact.Matchers.term + const like = pact.Matchers.somethingLike + const eachLike = pact.Matchers.eachLike // Animal we want to match :) const suitor = { @@ -47,15 +45,15 @@ describe('Pact', () => { 'walks in the garden/meadow', 'parkour' ] - }; + } - const MIN_ANIMALS = 2; + const MIN_ANIMALS = 2 // Define animal payload, with flexible matchers - // + // // This makes the test much more resilient to changes in actual data. // Here we specify the 'shape' of the object that we care about. - // It is also import here to not put in expectations for parts of the + // It is also import here to not put in expectations for parts of the // API we don't care about const animalBodyExpectation = { 'id': like(1), @@ -77,28 +75,22 @@ describe('Pact', () => { 'previously_married': like(false) }, 'interests': eachLike('walks in the garden/meadow') - }; + } // Define animal list payload, reusing existing object matcher const animalListExpectation = eachLike(animalBodyExpectation, { min: MIN_ANIMALS - }); + }) // Setup a Mock Server before unit tests run. // This server acts as a Test Double for the real Provider API. // We call addInteraction() to configure the Mock Service to act like the Provider // It also sets up expectations for what requests are to come, and will fail // if the calls are not seen. - before(done => { - mockServer.start() + before(() => { + return provider.setup() .then(() => { - provider = pact({ - consumer: 'Matching Service', - provider: 'Animal Profile Service', - port: MOCK_SERVER_PORT - }); - - return provider.addInteraction({ + provider.addInteraction({ state: 'Has some animals', uponReceiving: 'a request for all animals', withRequest: { @@ -108,14 +100,14 @@ describe('Pact', () => { willRespondWith: { status: 200, headers: { - 'Content-Type': 'application/json; charset=utf-8' + 'Content-Type': 'application/json charset=utf-8' }, body: animalListExpectation } - }); + }) }) .then(() => { - return provider.addInteraction({ + provider.addInteraction({ state: 'Has no animals', uponReceiving: 'a request for an animal with ID 100', withRequest: { @@ -125,10 +117,10 @@ describe('Pact', () => { willRespondWith: { status: 404 } - }); + }) }) .then(() => { - return provider.addInteraction({ + provider.addInteraction({ state: 'Has an animal with ID 1', uponReceiving: 'a request for an animal with ID 1', withRequest: { @@ -138,63 +130,66 @@ describe('Pact', () => { willRespondWith: { status: 200, headers: { - 'Content-Type': 'application/json; charset=utf-8' + 'Content-Type': 'application/json charset=utf-8' }, body: animalBodyExpectation } - }); + }) + }) + .catch(e =>{ + console.log('ERROR: ', e) }) - .then(() => done()) - .catch(e => { - console.log('ERROR: ', e); - done(); - }); - }); + }) // Configure and import consumer API // Note that we update the API endpoint to point at the Mock Service - process.env.API_HOST = `http://localhost:${MOCK_SERVER_PORT}`; + process.env.API_HOST = `http://localhost:${MOCK_SERVER_PORT}` const { suggestion, getAnimalById - } = require('../consumer'); + } = require('../consumer') // Verify service client works as expected. // - // Note that we don't call the consumer API endpoints directly, but - // use unit-style tests that test the collaborating function behaviour - + // Note that we don't call the consumer API endpoints directly, but + // use unit-style tests that test the collaborating function behaviour - // we want to test the function that is calling the external service. describe('when a call to list all animals from the Animal Service is made', () => { describe('and there are animals in the database', () => { it('returns a list of animals', done => { - const suggestedMates = suggestion(suitor); + const suggestedMates = suggestion(suitor) - expect(suggestedMates).to.eventually.have.deep.property('suggestions[0].score', 94); - expect(suggestedMates).to.eventually.have.property('suggestions').with.lengthOf(MIN_ANIMALS).notify(done); - }); - }); - }); + expect(suggestedMates).to.eventually.have.deep.property('suggestions[0].score', 94) + expect(suggestedMates).to.eventually.have.property('suggestions').with.lengthOf(MIN_ANIMALS).notify(done) + }) + }) + }) describe('when a call to the Animal Service is made to retreive a single animal by ID', () => { describe('and there is an animal in the DB with ID 1', () => { it('returns the animal', done => { - const suggestedMates = getAnimalById(1); + const suggestedMates = getAnimalById(1) - expect(suggestedMates).to.eventually.have.deep.property('id', 1).notify(done); - }); - }); + expect(suggestedMates).to.eventually.have.deep.property('id', 1).notify(done) + }) + }) describe('and there no animals in the database', () => { it('returns a 404', done => { - const suggestedMates = getAnimalById(100); + const suggestedMates = getAnimalById(100) - expect(suggestedMates).to.eventually.be.a('null').notify(done); - }); - }); - }); + expect(suggestedMates).to.eventually.be.a('null').notify(done) + }) + }) + }) + describe('when interacting with Animal Service', () => { + it('should validate the interactions and create a contract', () => { + // uncomment below to test a failed verify + // return getAnimalById(1123).then(provider.verify) + return provider.verify + }) + }) // Write pact files after(() => { - provider.finalize().then(() => { - mockservice.removeAllServers(); - }); - }); -}); + return provider.finalize() + }) +}) diff --git a/examples/e2e/test/provider.spec.js b/examples/e2e/test/provider.spec.js index 0ae3f448a..0c091ee7e 100644 --- a/examples/e2e/test/provider.spec.js +++ b/examples/e2e/test/provider.spec.js @@ -1,65 +1,66 @@ -const pact = require('@pact-foundation/pact-node'); -const path = require('path'); -const chai = require('chai'); -const chaiAsPromised = require('chai-as-promised'); -const expect = chai.expect; +const verifier = require('../../../src/pact.js').Verifier +const path = require('path') +const chai = require('chai') +const chaiAsPromised = require('chai-as-promised') +const expect = chai.expect +chai.use(chaiAsPromised) const { server, importData, animalRepository -} = require('../provider.js'); +} = require('../provider.js') // Append some extra endpoints to mutate current state of the API server.get('/states', (req, res) => { res.json({ "Matching Service": ['Has some animals', 'Has no animals', 'Has an animal with ID 1'] - }); -}); + }) +}) server.post('/setup', (req, res) => { - const state = req.body.state; + const state = req.body.state - animalRepository.clear(); + animalRepository.clear() switch (state) { case 'Has no animals': // do nothing - break; + break default: - importData(); + importData() } - res.end(); -}); + res.end() +}) server.listen(8081, () => { - console.log('Animal Profile Service listening on http://localhost:8081'); -}); + console.log('Animal Profile Service listening on http://localhost:8081') +}) // Verify that the provider meets all consumer expectations describe('Pact Verification', () => { it('should validate the expectations of Matching Service', function(done) { // lexical binding required here - this.timeout(10000); + this.timeout(10000) let opts = { providerBaseUrl: 'http://localhost:8081', providerStatesUrl: 'http://localhost:8081/states', providerStatesSetupUrl: 'http://localhost:8081/setup', - pactUrls: ['https://test.pact.dius.com.au/pacts/provider/Animal%20Profile%20Service/consumer/Matching%20Service/latest'], + // Remote pacts + // pactUrls: ['https://test.pact.dius.com.au/pacts/provider/Animal%20Profile%20Service/consumer/Matching%20Service/latest'], + // Local pacts + pactUrls: [path.resolve(process.cwd(), './pacts/matching_service-animal_profile_service.json')], pactBrokerUsername: 'dXfltyFMgNOFZAxr8io9wJ37iUpY42M', pactBrokerPassword: 'O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1' - }; + } - const verifyPromise = pact.verifyPacts(opts) - expect(verifyPromise).to.be.fulfilled; - - verifyPromise + verifier.verifyProvider(opts) .then(output => { - console.log('Pact Verification Complete!'); - console.log(output); - done(); + console.log('Pact Verification Complete!') + console.log(output) + done() }).catch(e => { - console.log('Pact Verification Failed: ', e); - done(); - }); - }); -}); + console.log('Pact Verification Failed: ', e) + done() + }) + }) +}) diff --git a/examples/e2e/test/publish.js b/examples/e2e/test/publish.js index 5eeb76aa3..7572e79ad 100644 --- a/examples/e2e/test/publish.js +++ b/examples/e2e/test/publish.js @@ -1,5 +1,5 @@ -const pact = require('@pact-foundation/pact-node'); -const path = require('path'); +const pact = require('@pact-foundation/pact-node') +const path = require('path') const opts = { pactUrls: [path.resolve(__dirname, '../pacts/matching_service-animal_profile_service.json')], pactBroker: 'https://test.pact.dius.com.au', @@ -7,17 +7,17 @@ const opts = { pactBrokerPassword: 'O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1', tags: ['prod', 'test'], consumerVersion: '1.0.0' -}; +} pact.publishPacts(opts) .then(() => { - console.log('Pact contract publishing complete!'); - console.log(''); - console.log('Head over to https://test.pact.dius.com.au/ and login with'); - console.log('=> Username: dXfltyFMgNOFZAxr8io9wJ37iUpY42M'); - console.log('=> Password: O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1'); - console.log('to see your published contracts.'); + console.log('Pact contract publishing complete!') + console.log('') + console.log('Head over to https://test.pact.dius.com.au/ and login with') + console.log('=> Username: dXfltyFMgNOFZAxr8io9wJ37iUpY42M') + console.log('=> Password: O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1') + console.log('to see your published contracts.') }) .catch(e => { - console.log('Pact contract publishing failed: ', e); - }); + console.log('Pact contract publishing failed: ', e) + }) diff --git a/examples/jest/__tests__/index.spec.js b/examples/jest/__tests__/index.spec.js index 40ffdcbcc..aa7c82be1 100644 --- a/examples/jest/__tests__/index.spec.js +++ b/examples/jest/__tests__/index.spec.js @@ -1,16 +1,14 @@ 'use strict' const path = require('path') -const Pact = require('pact') -const wrapper = require('@pact-foundation/pact-node') +const Pact = require('../../../src/pact.js') const getMeDogs = require('../index').getMeDogs describe("Dog's API", () => { let url = 'http://localhost' - let provider - const port = 8989; - const server = wrapper.createServer({ + const port = 8989 + const provider = Pact({ port: port, log: path.resolve(process.cwd(), 'logs', 'mockserver-integration.log'), dir: path.resolve(process.cwd(), 'pacts'), @@ -21,23 +19,12 @@ describe("Dog's API", () => { const EXPECTED_BODY = [{dog: 1}] - afterAll(() => { - wrapper.removeAllServers(); - }) - - afterEach(done => { - server.delete().then(done) - }); + beforeAll(() => provider.setup()) - beforeEach(done => { - server.start().then(() => { - provider = Pact({ consumer: 'MyConsumer', provider: 'MyProvider', port: port }) - done() - }) - }); + afterAll(() => provider.finalize()) describe("works", () => { - beforeEach(done => { + beforeAll(done => { const interaction = { state: 'i have a list of projects', uponReceiving: 'a request for projects', @@ -55,19 +42,18 @@ describe("Dog's API", () => { provider.addInteraction(interaction).then(done, done) }) - afterEach(done => { - return provider.finalize().then(done, done) - }) - - it('successfully verifies', done => { + // add expectations + it('returns a sucessful body', done => { return getMeDogs({ url, port }) - .then(provider.verify) .then(response => { - expect(response.headers['content-type']).toEqual('application/json'); - expect(response.data).toEqual(EXPECTED_BODY); - expect(response.status).toEqual(200); + expect(response.headers['content-type']).toEqual('application/json') + expect(response.data).toEqual(EXPECTED_BODY) + expect(response.status).toEqual(200) + done() }) - .then(done, done) }) + + // verify with Pact, and reset expectations + it('successfully verifies', () => provider.verify()) }) }) diff --git a/examples/jest/package.json b/examples/jest/package.json index 4e3f29431..6b7fb059d 100644 --- a/examples/jest/package.json +++ b/examples/jest/package.json @@ -11,9 +11,7 @@ "testEnvironment": "node" }, "devDependencies": { - "@pact-foundation/pact-node": "^4.5.3", "axios": "^0.14.0", - "jest-cli": "^15.1.1", - "pact": "^1.0.0-rc.5" + "jest-cli": "^15.1.1" } } diff --git a/examples/mocha/package.json b/examples/mocha/package.json index 0ad723158..b0b2212c2 100644 --- a/examples/mocha/package.json +++ b/examples/mocha/package.json @@ -8,10 +8,8 @@ }, "license": "MIT", "devDependencies": { - "@pact-foundation/pact-node": "^4.5.3", "axios": "^0.14.0", "chai": "^3.5.0", - "mocha": "^3.0.2", - "pact": "^1.0.0-rc.5" + "mocha": "^3.0.2" } } diff --git a/examples/mocha/test/index.spec.js b/examples/mocha/test/index.spec.js index 4e890500e..7807f30ab 100644 --- a/examples/mocha/test/index.spec.js +++ b/examples/mocha/test/index.spec.js @@ -2,16 +2,14 @@ const expect = require('chai').expect const path = require('path') -const Pact = require('pact') -const wrapper = require('@pact-foundation/pact-node') +const Pact = require('../../../src/pact.js') const getMeDogs = require('../index').getMeDogs describe('The Dog API', () => { let url = 'http://localhost' - let provider - const port = 8989 - const server = wrapper.createServer({ + + const provider = Pact({ port: port, log: path.resolve(process.cwd(), 'logs', 'mockserver-integration.log'), dir: path.resolve(process.cwd(), 'pacts'), @@ -22,23 +20,12 @@ describe('The Dog API', () => { const EXPECTED_BODY = [{dog: 1}] - after(() => { - wrapper.removeAllServers() - }) + before(() => provider.setup()) - afterEach(done => { - server.delete().then(() => { done() }) - }) - - beforeEach(done => { - server.start().then(() => { - provider = Pact({ consumer: 'MyConsumer', provider: 'MyProvider', port: port }) - done() - }) - }) + after(() => provider.finalize()) describe('works', () => { - beforeEach(done => { + before(done => { const interaction = { state: 'i have a list of projects', uponReceiving: 'a request for projects', @@ -56,19 +43,18 @@ describe('The Dog API', () => { provider.addInteraction(interaction).then(() => { done() }) }) - afterEach(done => { - provider.finalize().then(() => { done() }) - }) - it('successfully verifies', done => { + it('returns the correct response', done => { const urlAndPort = { url: url, port: port } getMeDogs(urlAndPort) - .then(provider.verify) .then(response => { - expect(response).to.eql(JSON.stringify(EXPECTED_BODY)) + expect(response.data).to.eql(EXPECTED_BODY) done() }) .catch(done) }) + + // verify with Pact, and reset expectations + it('successfully verifies', () => provider.verify()) }) }) diff --git a/karma/jasmine/client-spec.js b/karma/jasmine/client-spec.js index eba1b2690..ded57e65d 100644 --- a/karma/jasmine/client-spec.js +++ b/karma/jasmine/client-spec.js @@ -3,26 +3,26 @@ describe("Client", function() { - var client, projectsProvider; + var client, provider beforeAll(function(done) { client = example.createClient('http://localhost:1234') - projectsProvider = Pact({ consumer: 'Karma Jasmine', provider: 'Hello' }) + provider = Pact({ consumer: 'Karma Jasmine', provider: 'Hello' }) // required for slower Travis CI environment setTimeout(function () { done() }, 2000) - }); + }) afterAll(function (done) { - projectsProvider.finalize() + provider.finalize() .then(function () { done() }, function (err) { done.fail(err) }) - }); + }) describe("sayHello", function () { - beforeEach(function (done) { - projectsProvider.addInteraction({ + beforeAll(function (done) { + provider.addInteraction({ uponReceiving: 'a request for hello', withRequest: { - method: 'get', + method: 'GET', path: '/sayHello' }, willRespondWith: { @@ -37,26 +37,27 @@ it("should say hello", function(done) { //Run the tests client.sayHello() - .then(projectsProvider.verify) .then(function (data) { - expect(JSON.parse(data)).toEqual({ reply: "Hello" }); + expect(JSON.parse(data.responseText)).toEqual({ reply: "Hello" }) done() }) .catch(function (err) { done.fail(err) }) - }); - }); + }) + + // verify with Pact, and reset expectations + it('successfully verifies', function() { provider.verify() }) + }) describe("findFriendsByAgeAndChildren", function () { - beforeEach(function (done) { - //Add interaction - projectsProvider + beforeAll(function (done) { + provider .addInteraction({ uponReceiving: 'a request friends', withRequest: { - method: 'get', + method: 'GET', path: '/friends', query: { age: Pact.Matchers.term({generate: '30', matcher: '\\d+'}), //remember query params are always strings @@ -80,79 +81,88 @@ it("should return some friends", function(done) { //Run the tests client.findFriendsByAgeAndChildren('33', ['Mary Jane', 'James']) - .then(projectsProvider.verify) - .then(function (data) { - expect(JSON.parse(data)).toEqual({friends: [{ name: 'Sue' }]}); + .then(function (res) { + expect(JSON.parse(res.responseText)).toEqual({friends: [{ name: 'Sue' }]}) done() }) .catch(function (err) { done.fail(err) }) - }); - }); + }) - describe("unfriendMe", function () { + // verify with Pact, and reset expectations + it('successfully verifies', function() { provider.verify() }) + }) - beforeEach(function (done) { - //Add interaction - projectsProvider.addInteraction({ - state: 'I am friends with Fred', - uponReceiving: 'a request to unfriend', - withRequest: { - method: 'put', - path: '/unfriendMe' - }, - willRespondWith: { - status: 200, - headers: { "Content-Type": "application/json" }, - body: { reply: "Bye" } - } - }) - .then(function () { done() }, function (err) { done.fail(err) }) - }) + describe("unfriendMe", function () { - it("should unfriend me", function(done) { - //Run the tests - client.unfriendMe() - .then(projectsProvider.verify) - .then(function (data) { - expect(JSON.parse(data)).toEqual({ reply: "Bye" }); - done() - }) - .catch(function (err) { - done.fail(err) - }) - }); + describe("when I have some friends", function () { - xdescribe("when there are no friends", function () { - beforeEach(function (done) { - projectsProvider.addInteraction({ - state: 'I have no friends', + beforeAll(function (done) { + //Add interaction + provider.addInteraction({ + state: 'I am friends with Fred', uponReceiving: 'a request to unfriend', withRequest: { - method: 'put', + method: 'PUT', path: '/unfriendMe' }, willRespondWith: { - status: 404 + status: 200, + headers: { "Content-Type": "application/json" }, + body: { reply: "Bye" } } }) .then(function () { done() }, function (err) { done.fail(err) }) }) - it("returns an error message", function (done) { + it("should unfriend me", function(done) { //Run the tests client.unfriendMe() - .catch(projectsProvider.verify) - .then(function (data) { - expect(data).toEqual('No friends :('); + .then(function (res) { + expect(JSON.parse(res.responseText)).toEqual({ reply: "Bye" }) done() }) .catch(function (err) { done.fail(err) }) - }); - }); - }); - }); -})(); + }) + + it('successfully verifies', function() { provider.verify() }) + }) + + // verify with Pact, and reset expectations + describe("when there are no friends", function () { + beforeAll(function (done) { + //Add interaction + provider.addInteraction({ + state: 'I have no friends', + uponReceiving: 'a request to unfriend', + withRequest: { + method: 'put', + path: '/unfriendMe' + }, + willRespondWith: { + status: 404 + } + }) + .then(function () { done() }, function (err) { done.fail(err) }) + }) + + it("returns an error message", function (done) { + //Run the tests + client.unfriendMe().then(function() { + done(new Error('expected request to /unfriend me to fail')) + }, function(e) { + done() + }) + + }) + + // verify with Pact, and reset expectations + it('successfully verifies', function() { provider.verify() }) + }) + }) + + }) +})() diff --git a/karma/mocha/client-spec.js b/karma/mocha/client-spec.js index faa6c0597..5262434d4 100644 --- a/karma/mocha/client-spec.js +++ b/karma/mocha/client-spec.js @@ -3,26 +3,26 @@ describe("Client", function() { - var client, projectsProvider; + var client, provider before(function(done) { - client = example.createClient('http://localhost:1234'); - projectsProvider = Pact({ consumer: 'Karma Mocha', provider: 'Hello' }) + client = example.createClient('http://localhost:1234') + provider = Pact({ consumer: 'Karma Mocha', provider: 'Hello' }) // required for slower Travis CI environment setTimeout(function () { done() }, 1000) }) after(function (done) { - projectsProvider.finalize() + provider.finalize() .then(function () { done() }, function (err) { done(err) }) }) describe("sayHello", function () { - beforeEach(function (done) { - projectsProvider.addInteraction({ + before(function (done) { + provider.addInteraction({ uponReceiving: 'a request for hello', withRequest: { - method: 'get', + method: 'GET', path: '/sayHello' }, willRespondWith: { @@ -37,25 +37,27 @@ it("should say hello", function(done) { //Run the tests client.sayHello() - .then(projectsProvider.verify) .then(function (data) { - expect(JSON.parse(data)).to.eql({ reply: "Hello" }); + expect(JSON.parse(data.responseText)).to.eql({ reply: "Hello" }) done() }) .catch(function (err) { done(err) }) - }); - }); + }) + + // verify with Pact, and reset expectations + it('successfully verifies', function() { provider.verify() }) + }) describe("findFriendsByAgeAndChildren", function () { - beforeEach(function (done) { - projectsProvider + before(function (done) { + provider .addInteraction({ uponReceiving: 'a request friends', withRequest: { - method: 'get', + method: 'GET', path: '/friends', query: { age: Pact.Matchers.term({generate: '30', matcher: '\\d+'}), //remember query params are always strings @@ -79,54 +81,61 @@ it("should return some friends", function(done) { //Run the tests client.findFriendsByAgeAndChildren('33', ['Mary Jane', 'James']) - .then(projectsProvider.verify) - .then(function (data) { - expect(JSON.parse(data)).to.eql({friends: [{ name: 'Sue' }]}); + .then(function (res) { + expect(JSON.parse(res.responseText)).to.eql({friends: [{ name: 'Sue' }]}) done() }) .catch(function (err) { done(err) }) - }); - }); + }) + + // verify with Pact, and reset expectations + it('successfully verifies', function() { provider.verify() }) + }) describe("unfriendMe", function () { - beforeEach(function (done) { - //Add interaction - projectsProvider.addInteraction({ - state: 'I am friends with Fred', - uponReceiving: 'a request to unfriend', - withRequest: { - method: 'put', - path: '/unfriendMe' - }, - willRespondWith: { - status: 200, - headers: { "Content-Type": "application/json" }, - body: { reply: "Bye" } - } - }) - .then(function () { done() }, function (err) { done(err) }) - }) + describe("when I have some friends", function () { - it("should unfriend me", function(done) { - //Run the tests - client.unfriendMe() - .then(projectsProvider.verify) - .then(function (data) { - expect(JSON.parse(data)).to.eql({ reply: "Bye" }) - done() - }) - .catch(function (err) { - done(err) + before(function (done) { + //Add interaction + provider.addInteraction({ + state: 'I am friends with Fred', + uponReceiving: 'a request to unfriend', + withRequest: { + method: 'PUT', + path: '/unfriendMe' + }, + willRespondWith: { + status: 200, + headers: { "Content-Type": "application/json" }, + body: { reply: "Bye" } + } }) - }); + .then(function () { done() }, function (err) { done(err) }) + }) + + it("should unfriend me", function(done) { + //Run the tests + client.unfriendMe() + .then(function (res) { + expect(JSON.parse(res.responseText)).to.eql({ reply: "Bye" }) + done() + }) + .catch(function (err) { + done(err) + }) + }) + + it('successfully verifies', function() { provider.verify() }) + }) - xdescribe("when there are no friends", function () { - beforeEach(function (done) { + // verify with Pact, and reset expectations + describe("when there are no friends", function () { + before(function (done) { //Add interaction - projectsProvider.addInteraction({ + provider.addInteraction({ state: 'I have no friends', uponReceiving: 'a request to unfriend', withRequest: { @@ -142,15 +151,18 @@ it("returns an error message", function (done) { //Run the tests - client.unfriendMe() - .catch(projectsProvider.verify) - .then(function (data) { - expect(data).to.eql('No friends :(') - done() - }) - }); - }); - }); + client.unfriendMe().then(function() { + done(new Error('expected request to /unfriend me to fail')) + }, function(e) { + done() + }) + + }) + + // verify with Pact, and reset expectations + it('successfully verifies', function() { provider.verify() }) + }) + }) - }); -})(); + }) +})() diff --git a/package.json b/package.json index 4e4429698..e0294d2a8 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "jscpd": "jscpd -p src -r json -o jscpd.json", "predist": "npm run clean && npm run lint && npm run jscpd", "postdist": "npm t", - "test": "istanbul cover ./node_modules/.bin/_mocha -- ./test", + "test": "istanbul cover ./node_modules/.bin/_mocha --timeout 10000 -- ./test", "test:karma": "npm run test:karma:jasmine && npm run test:karma:mocha", "test:karma:jasmine": "karma start ./karma/jasmine/karma.conf.js", "test:karma:mocha": "karma start ./karma/mocha/karma.conf.js", @@ -100,6 +100,7 @@ "bluebird": "3.x", "chai": "3.x", "chai-as-promised": "5.x", + "cli-color": "^1.1.0", "coveralls": "2.x", "documentation": "4.0.0-beta9", "imports-loader": "0.x", diff --git a/src/common/responseParser.js b/src/common/responseParser.js deleted file mode 100644 index 2fcaa8e9e..000000000 --- a/src/common/responseParser.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict' - -function getResponseText (response) { - if (typeof response === 'string') { - return response - } - return response.text || response.responseText || '' -} - -function processResponse (response) { - let responseText = getResponseText(response) - if (responseText.indexOf('interaction_diffs') > -1) { - return { action: 'reject', content: responseText } - } - - return { action: 'resolve', content: responseText } -} - -module.exports.parse = (response) => { - if (Array.isArray(response)) { - let processedResponses = response.map(processResponse) - let erroredResponses = processedResponses - .filter((r) => r.action === 'reject') - .map((r) => r.content) - - if (erroredResponses.length) { - return Promise.reject(erroredResponses) - } else { - return Promise.resolve(processedResponses.map((r) => r.content)) - } - } else { - let result = processResponse(response) - return Promise[result.action](result.content) - } -} diff --git a/src/dsl/mockService.js b/src/dsl/mockService.js index 57ecec9e2..e580eece4 100644 --- a/src/dsl/mockService.js +++ b/src/dsl/mockService.js @@ -1,6 +1,7 @@ /** - * A Mock Service is the interaction mechanism through which pacts get written and verified. - * This should be transparent to the end user. + * Mock Service is the HTTP interface to setup the Pact Mock Service. + * See https://github.com/bethesque/pact-mock_service and + * https://gist.github.com/bethesque/9d81f21d6f77650811f4. * @module MockService */ diff --git a/src/dsl/verifier.js b/src/dsl/verifier.js new file mode 100644 index 000000000..c8a630318 --- /dev/null +++ b/src/dsl/verifier.js @@ -0,0 +1,12 @@ +/** + * Provider Verifier service + * @module ProviderVerifier + */ + +'use strict' + +const serviceFactory = require('@pact-foundation/pact-node') + +module.exports.verifyProvider = (opts) => { + return serviceFactory.verifyPacts(opts) +} diff --git a/src/pact-karma.js b/src/pact-karma.js new file mode 100644 index 000000000..29f7daa6d --- /dev/null +++ b/src/pact-karma.js @@ -0,0 +1,124 @@ +/** + * Pact module for Karma use. + * @module Pact Karma + */ + +'use strict' + +require('es6-promise').polyfill() + +var isNil = require('lodash.isnil') +var logger = require('./common/logger') +var Matchers = require('./dsl/matchers') +var MockService = require('./dsl/mockService') +var Interaction = require('./dsl/interaction') +/** + * Creates a new {@link PactProvider}. + * @memberof Pact + * @name create + * @param {Object} opts + * @param {string} opts.consumer - the name of the consumer + * @param {string} opts.provider - the name of the provider + * @param {number} opts.port - port of the mock service, defaults to 1234 + * @param {string} opts.host - host address of the mock service, defaults to 127.0.0.1 + * @param {boolean} opts.ssl - SSL flag to identify the protocol to be used (default false, HTTP) + * @return {@link PactProvider} + * @static + */ +module.exports = (opts) => { + var consumer = opts.consumer + var provider = opts.provider + + if (isNil(consumer)) { + throw new Error('You must provide a Consumer for this pact.') + } + + if (isNil(provider)) { + throw new Error('You must provide a Provider for this pact.') + } + + var port = opts.port || 1234 + var host = opts.host || '127.0.0.1' + var ssl = opts.ssl || false + + logger.info(`Setting up Pact with Consumer "${consumer}" and Provider "${provider}" using mock service on Port: "${port}"`) + + const mockService = new MockService(consumer, provider, port, host, ssl) + + /** @namespace PactProvider */ + return { + /** + * Add an interaction to the {@link MockService}. + * @memberof PactProvider + * @instance + * @param {Interaction} interactionObj + * @returns {Promise} + */ + addInteraction: (interactionObj) => { + let interaction = new Interaction() + + if (interactionObj.state) { + interaction.given(interactionObj.state) + } + + interaction + .uponReceiving(interactionObj.uponReceiving) + .withRequest(interactionObj.withRequest) + .willRespondWith(interactionObj.willRespondWith) + + return mockService.addInteraction(interaction) + }, + /** + * Checks with the Mock Service if the expected interactions have been exercised. + * @memberof PactProvider + * @instance + * @returns {Promise} + */ + verify: () => { + return mockService.verify() + .then(() => { mockService.removeInteractions() }) + .catch(e => { + // Properly format the error + console.error('') + console.error('Pact verification failed!') + console.error(e) + + throw new Error('Pact verification failed - expected interactions did not match actual.') + }) + }, + /** + * Writes the Pact and clears any interactions left behind. + * @memberof PactProvider + * @instance + * @returns {Promise} + */ + finalize: () => { + return mockService.writePact().then(() => mockService.removeInteractions()) + }, + /** + * Writes the Pact file but leave interactions in. + * @memberof PactProvider + * @instance + * @returns {Promise} + */ + writePact: () => { + return mockService.writePact() + }, + /** + * Clear up any interactions in the Provider Mock Server. + * @memberof PactProvider + * @instance + * @returns {Promise} + */ + removeInteractions: () => { + return mockService.removeInteractions() + } + } +} + +/** + * Exposes {@link Matchers#term} + * @memberof Pact + * @static + */ +module.exports.Matchers = Matchers diff --git a/src/pact.js b/src/pact.js index 2066ec323..8faef69cb 100644 --- a/src/pact.js +++ b/src/pact.js @@ -7,12 +7,15 @@ require('es6-promise').polyfill() -var isNil = require('lodash.isnil') -var logger = require('./common/logger') -var Matchers = require('./dsl/matchers') -var MockService = require('./dsl/mockService') -var Interaction = require('./dsl/interaction') -var responseParser = require('./common/responseParser').parse +const isNil = require('lodash.isnil') +const logger = require('./common/logger') +const Matchers = require('./dsl/matchers') +const Verifier = require('./dsl/verifier') +const MockService = require('./dsl/mockService') +const Interaction = require('./dsl/interaction') +const serviceFactory = require('@pact-foundation/pact-node') +const clc = require('cli-color') +const path = require('path') /** * Creates a new {@link PactProvider}. @@ -28,20 +31,32 @@ var responseParser = require('./common/responseParser').parse * @static */ module.exports = (opts) => { - var consumer = opts.consumer - var provider = opts.provider + const consumer = opts.consumer + const provider = opts.provider if (isNil(consumer)) { - throw new Error('You must inform a Consumer for this Pact.') + throw new Error('You must specify a Consumer for this pact.') } if (isNil(provider)) { - throw new Error('You must inform a Provider for this Pact.') + throw new Error('You must specify a Provider for this pact.') } - var port = opts.port || 1234 - var host = opts.host || '127.0.0.1' - var ssl = opts.ssl || false + const port = opts.port || 1234 + const host = opts.host || '127.0.0.1' + const ssl = opts.ssl || false + const dir = opts.dir || path.resolve(process.cwd(), 'pacts') + const log = opts.log || path.resolve(process.cwd(), 'logs', 'pact.log') + const logLevel = opts.logLevel || 'INFO' + const spec = opts.spec || 2 + const server = serviceFactory.createServer({ + port: port, + log: log, + dir: dir, + spec: spec, + ssl: ssl + }) + serviceFactory.logLevel(logLevel) logger.info(`Setting up Pact with Consumer "${consumer}" and Provider "${provider}" using mock service on Port: "${port}"`) @@ -49,6 +64,13 @@ module.exports = (opts) => { /** @namespace PactProvider */ return { + + /** + * Start the Mock Server. + * @returns {Promise} + */ + setup: () => server.start(), + /** * Add an interaction to the {@link MockService}. * @memberof PactProvider @@ -70,51 +92,62 @@ module.exports = (opts) => { return mockService.addInteraction(interaction) }, + /** - * Executes a promise chain that will eventually write the Pact file if successful. + * Checks with the Mock Service if the expected interactions have been exercised. * @memberof PactProvider * @instance - * @param {Response|Response[]} response - the response object or an Array of response objects of the Request(s) issued * @returns {Promise} */ - verify: (response) => { - let integrationFnResult + verify: () => { + return mockService.verify() + .then(() => { mockService.removeInteractions() }) + .catch(e => { + // Properly format the error + console.error('') + console.error(clc.red('Pact verification failed!')) + console.error(clc.red(e)) - return responseParser(response) - .then((res) => { integrationFnResult = res }) - .then(() => mockService.verify()) - .then(() => integrationFnResult) + throw new Error('Pact verification failed - expected interactions did not match actual.') + }) }, + /** - * Writes the Pact and clears any interactions left behind. + * Writes the Pact and clears any interactions left behind and shutdown the + * mock server * @memberof PactProvider * @instance * @returns {Promise} */ - finalize: () => { - return mockService.writePact().then(() => mockService.removeInteractions()) - }, + finalize: () => mockService.writePact().then(() => server.delete()), + /** - * Writes the Pact file but leave interactions in. + * Writes the pact file out to file. Should be called when all tests have been performed for a + * given Consumer <-> Provider pair. It will write out the Pact to the + * configured file. * @memberof PactProvider * @instance * @returns {Promise} */ - writePact: () => { - return mockService.writePact() - }, + writePact: () => mockService.writePact(), + /** * Clear up any interactions in the Provider Mock Server. * @memberof PactProvider * @instance * @returns {Promise} */ - removeInteractions: () => { - return mockService.removeInteractions() - } + removeInteractions: () => mockService.removeInteractions() } } +/** + * Exposes {@link Verifier} + * @memberof Pact + * @static + */ +module.exports.Verifier = Verifier + /** * Exposes {@link Matchers#term} * @memberof Pact diff --git a/test/common/request.spec.js b/test/common/request.spec.js index 5afdf0273..0e9fac2e4 100644 --- a/test/common/request.spec.js +++ b/test/common/request.spec.js @@ -18,11 +18,11 @@ describe('Request', () => { } request = new Request() - }); + }) after(() => { global.window = undefined - }); + }) it('should have "XMLHttpRequest" _request', function () { expect(request._request).to.not.be.null diff --git a/test/common/responseParser.spec.js b/test/common/responseParser.spec.js deleted file mode 100644 index f2b2a65a1..000000000 --- a/test/common/responseParser.spec.js +++ /dev/null @@ -1,61 +0,0 @@ -'use strict' - -var expect = require('chai').expect -var request = require('superagent') -var parse = require('../../src/common/responseParser').parse - -describe('Response Parser', () => { - - context('when Array of responses', () => { - - it('resolves Promise with successful responses', (done) => { - let responses = [ - { text: '{ "this": "is", "a": "json response" }' }, - { text: '{ "this": "is", "another": "json response" }' } - ] - expect(parse(responses)).to.eventually.eql(responses.map((r) => r.text)).notify(done) - }) - - it('resolves Promise with mixed responses', (done) => { - let responses = [ - { text: '{ "this": "is", "a": "json response" }' }, - { text: '{ "message": "error", "interaction_diffs": [] }' } - ] - expect(parse(responses)).to.eventually.be.rejected.notify(done) - }) - - }) - - context('when single response', () => { - - it('resolves Promise with response from node', (done) => { - let response = { text: '{ "this": "is", "a": "json response" }' } - expect(parse(response)).to.eventually.eql(response.text).notify(done) - }) - - it('resolves Promise with response from browser', (done) => { - let response = { responseText: '{ "this": "is", "a": "json response" }' } - expect(parse(response)).to.eventually.eql(response.responseText).notify(done) - }) - - it('resolves Promise when response is a string', (done) => { - let response = '{ "json": "string" }' - expect(parse(response)).to.eventually.eql(response).notify(done) - }) - - const bodyArr = [ '', undefined, null ] - bodyArr.forEach((body) => { - it(`resolves Promise when body is "${body}"`, (done) => { - let response = { text: body } - expect(parse(response)).to.eventually.eql('').notify(done) - }) - }) - - it('rejects Promise when there are "interaction_diffs"', (done) => { - let response = { responseText: '{ "message": "error", "interaction_diffs": [] }' } - expect(parse(response)).to.eventually.be.rejected.notify(done) - }) - - }) - -}) diff --git a/test/dsl/integration.spec.js b/test/dsl/integration.spec.js index 1cc5b3b36..0d9c1bc2c 100644 --- a/test/dsl/integration.spec.js +++ b/test/dsl/integration.spec.js @@ -4,7 +4,6 @@ var path = require('path') var expect = require('chai').expect var Promise = require('bluebird') var request = require('superagent') -var wrapper = require('@pact-foundation/pact-node') var Pact = require('../../src/pact') var Matchers = Pact.Matchers @@ -12,278 +11,247 @@ var Matchers = Pact.Matchers describe('Integration', () => { ['http', 'https'].forEach((PROTOCOL) => { - - describe(`Pact on ${PROTOCOL} protocol`, (protocol) => { - - const MOCK_PORT = Math.floor(Math.random() * 999) + 9000 - const PROVIDER_URL = `${PROTOCOL}://localhost:${MOCK_PORT}` - const mockServer = wrapper.createServer({ - port: MOCK_PORT, - log: path.resolve(process.cwd(), 'logs', 'mockserver-integration.log'), - dir: path.resolve(process.cwd(), 'pacts'), - ssl: PROTOCOL === 'https' ? true : false, - spec: 2 - }) - - const EXPECTED_BODY = [{ - id: 1, - name: 'Project 1', - due: '2016-02-11T09:46:56.023Z', - tasks: [ - {id: 1, name: 'Do the laundry', 'done': true}, - {id: 2, name: 'Do the dishes', 'done': false}, - {id: 3, name: 'Do the backyard', 'done': false}, - {id: 4, name: 'Do nothing', 'done': false} - ] - }] - - var provider, counter = 1 - - after(() => { - wrapper.removeAllServers() - }) - - beforeEach((done) => { - mockServer.start().then(() => { - provider = Pact({ consumer: `Consumer ${counter}`, - provider: `Provider ${counter}`, - port: MOCK_PORT, - ssl: PROTOCOL === 'https' ? true : false - }) - done() + describe(`Pact on ${PROTOCOL} protocol`, (protocol) => { + + const MOCK_PORT = Math.floor(Math.random() * 999) + 9000 + const PROVIDER_URL = `${PROTOCOL}://localhost:${MOCK_PORT}` + const provider = Pact({ + consumer: 'Matching Service', + provider: 'Animal Profile Service', + port: MOCK_PORT, + log: path.resolve(process.cwd(), 'logs', 'mockserver-integration.log'), + dir: path.resolve(process.cwd(), 'pacts'), + logLevel: 'INFO', + ssl: (PROTOCOL === 'https'), + spec: 2 }) - }) - afterEach((done) => { - mockServer.delete().then(() => { - counter++ - done() - }) - }) + const EXPECTED_BODY = [{ + id: 1, + name: 'Project 1', + due: '2016-02-11T09:46:56.023Z', + tasks: [ + {id: 1, name: 'Do the laundry', 'done': true}, + {id: 2, name: 'Do the dishes', 'done': false}, + {id: 3, name: 'Do the backyard', 'done': false}, + {id: 4, name: 'Do nothing', 'done': false} + ] + }] + + let counter = 1 + + before(provider.setup) + + // once all tests are run, write pact and remove interactions + after(() => provider.finalize()) + + context('with a single request', () => { + + // add interactions, as many as needed + before((done) => { + provider.addInteraction({ + state: 'i have a list of projects', + uponReceiving: 'a request for projects', + withRequest: { + method: 'get', + path: '/projects', + headers: { 'Accept': 'application/json' } + }, + willRespondWith: { + status: 200, + headers: { 'Content-Type': 'application/json' }, + body: EXPECTED_BODY + } + }) + .then(() => done()) + }) - context('with a single request', () => { - - // add interactions, as many as needed - beforeEach((done) => { - provider.addInteraction({ - state: 'i have a list of projects', - uponReceiving: 'a request for projects', - withRequest: { - method: 'get', - path: '/projects', - headers: { 'Accept': 'application/json' } - }, - willRespondWith: { - status: 200, - headers: { 'Content-Type': 'application/json' }, - body: EXPECTED_BODY - } - }).then(() => done()) - }) + // execute your assertions + it('returns the correct body', (done) => { + request + .get(`${PROVIDER_URL}/projects`) + .set({ 'Accept': 'application/json' }) + .then((res) => { + expect(res.text).to.eql(JSON.stringify(EXPECTED_BODY)) + }) + .then(done) + }) - // once test is run, write pact and remove interactions - afterEach((done) => { - provider.finalize().then(() => done()) + // verify with Pact, and reset expectations + it('successfully verifies', () => provider.verify()) }) - // execute your assertions - it('successfully verifies', (done) => { - const verificationPromise = request - .get(`${PROVIDER_URL}/projects`) - .set({ 'Accept': 'application/json' }) - .then(provider.verify) - - expect(verificationPromise).to.eventually.eql(JSON.stringify(EXPECTED_BODY)).notify(done) - }) - }) + context('with a single request with query string parameters', () => { + + // add interactions, as many as needed + before((done) => { + provider.addInteraction({ + state: 'i have a list of projects', + uponReceiving: 'a request for projects with a filter', + withRequest: { + method: 'get', + path: '/projects', + query: { from: 'today' }, + headers: { 'Accept': 'application/json' } + }, + willRespondWith: { + status: 200, + headers: { 'Content-Type': 'application/json' }, + body: EXPECTED_BODY + } + }) + .then(() => done()) + }) - context('with a single request with query string parameters', () => { - - // add interactions, as many as needed - beforeEach((done) => { - provider.addInteraction({ - state: 'i have a list of projects', - uponReceiving: 'a request for projects', - withRequest: { - method: 'get', - path: '/projects', - query: { from: 'today' }, - headers: { 'Accept': 'application/json' } - }, - willRespondWith: { - status: 200, - headers: { 'Content-Type': 'application/json' }, - body: EXPECTED_BODY - } - }).then(() => done()) - }) + // execute your assertions + it('returns the correct body', (done) => { + request + .get(`${PROVIDER_URL}/projects?from=today`) + .set({ 'Accept': 'application/json' }) + .then((res) => { + expect(res.text).to.eql(JSON.stringify(EXPECTED_BODY)) + }) + .then(done) + }) - // once test is run, write pact and remove interactions - afterEach((done) => { - provider.finalize().then(() => done()) + // verify with Pact, and reset expectations + it('successfully verifies', () => provider.verify()) }) - // execute your assertions - it('successfully verifies', (done) => { - const verificationPromise = request - .get(`${PROVIDER_URL}/projects?from=today`) - .set({ 'Accept': 'application/json' }) - .then(provider.verify) + context('with a single request and matchers', () => { + + // add interactions, as many as needed + before((done) => { + provider.addInteraction({ + state: 'i have a list of projects but I dont know how many', + uponReceiving: 'a request for such projects', + withRequest: { + method: 'get', + path: '/projects', + headers: { 'Accept': 'application/json' } + }, + willRespondWith: { + status: 200, + headers: { 'Content-Type': Matchers.term({ generate: 'application/json', matcher: 'application\/json' }) }, + body: [{ + id: 1, + name: 'Project 1', + due: '2016-02-11T09:46:56.023Z', + tasks: Matchers.eachLike({ + id: Matchers.somethingLike(1), + name: Matchers.somethingLike('Do the laundry'), + 'done': Matchers.somethingLike(true) + }, { min: 4 }) + }] + } + }).then(() => done()) + }) - expect(verificationPromise).to.eventually.eql(JSON.stringify(EXPECTED_BODY)).notify(done) - }) - }) + // execute your assertions + it('returns the correct body', (done) => { + const verificationPromise = request + .get(`${PROVIDER_URL}/projects`) + .set({ 'Accept': 'application/json' }) + .then((res) => { + return JSON.parse(res.text)[0] + }) - context('with a single request and matchers', () => { - - // add interactions, as many as needed - beforeEach((done) => { - provider.addInteraction({ - state: 'i have a list of projects but I dont know how many', - uponReceiving: 'a request for such projects', - withRequest: { - method: 'get', - path: '/projects', - headers: { 'Accept': 'application/json' } - }, - willRespondWith: { - status: 200, - headers: { 'Content-Type': Matchers.term({ generate: 'application/json', matcher: 'application\/json' }) }, - body: [{ - id: 1, - name: 'Project 1', - due: '2016-02-11T09:46:56.023Z', - tasks: Matchers.eachLike({ - id: Matchers.somethingLike(1), - name: Matchers.somethingLike('Do the laundry'), - 'done': Matchers.somethingLike(true) - }, { min: 4 }) - }] - } - }).then(() => done()) - }) + expect(verificationPromise).to.eventually.have.lengthOf(4) + expect(verificationPromise).to.eventually.have.property('tasks').notify(done) + }) - // once test is run, write pact and remove interactions - afterEach((done) => { - provider.finalize().then(() => done()) + // verify with Pact, and reset expectations + it('successfully verifies', () => provider.verify()) }) - // execute your assertions - it('successfully verifies', (done) => { - const verificationPromise = request - .get(`${PROVIDER_URL}/projects`) - .set({ 'Accept': 'application/json' }) - .then(provider.verify) - - verificationPromise.then((data) => { - let jsonData = JSON.parse(data)[0] - expect(jsonData).to.have.property('tasks') - expect(jsonData.tasks).to.have.lengthOf(4) - done() + context('with two requests', () => { + + before((done) => { + let interaction1 = provider.addInteraction({ + state: 'i have a list of projects', + uponReceiving: 'a request for projects', + withRequest: { + method: 'get', + path: '/projects', + headers: { 'Accept': 'application/json' } + }, + willRespondWith: { + status: 200, + headers: { 'Content-Type': 'application/json' }, + body: EXPECTED_BODY + } + }) + + let interaction2 = provider.addInteraction({ + state: 'i have a list of projects', + uponReceiving: 'a request for a project that does not exist', + withRequest: { + method: 'get', + path: '/projects/2', + headers: { 'Accept': 'application/json' } + }, + willRespondWith: { + status: 404, + headers: { 'Content-Type': 'application/json' } + } + }) + + Promise.all([interaction1, interaction2]).then(() => done()) }) - }) - }) - context('with two requests', () => { - - beforeEach((done) => { - let interaction1 = provider.addInteraction({ - state: 'i have a list of projects', - uponReceiving: 'a request for projects', - withRequest: { - method: 'get', - path: '/projects', - headers: { 'Accept': 'application/json' } - }, - willRespondWith: { - status: 200, - headers: { 'Content-Type': 'application/json' }, - body: EXPECTED_BODY - } - }) + it('allows two requests', (done) => { + const verificationPromise = + request.get(`${PROVIDER_URL}/projects`) + .set({ 'Accept': 'application/json' }) + .then((res) => { + return res.text + }) + expect(verificationPromise).to.eventually.eql(JSON.stringify(EXPECTED_BODY)).notify(done) - let interaction2 = provider.addInteraction({ - state: 'i have a list of projects', - uponReceiving: 'a request for a project that does not exist', - withRequest: { - method: 'get', - path: '/projects/2', - headers: { 'Accept': 'application/json' } - }, - willRespondWith: { - status: 404, - headers: { 'Content-Type': 'application/json' } - } + const verificationPromise404 = + request.get(`${PROVIDER_URL}/projects/2`).set({ 'Accept': 'application/json' }) + expect(verificationPromise404).to.eventually.be.rejected }) - Promise.all([interaction1, interaction2]).then(() => done()) + // verify with Pact, and reset expectations + it('successfully verifies', () => provider.verify()) }) - // once test is run, write pact and remove interactions - afterEach((done) => { - provider.finalize().then(() => done()) - }) + context('with an unexpected interaction', () => { + // add interactions, as many as needed + before((done) => { + provider.addInteraction({ + state: 'i have a list of projects', + uponReceiving: 'a request for projects', + withRequest: { + method: 'get', + path: '/projects', + headers: { 'Accept': 'application/json' } + }, + willRespondWith: { + status: 200, + headers: { 'Content-Type': 'application/json' }, + body: EXPECTED_BODY + } + }).then(() => done()) + }) - it('successfully verifies', (done) => { - let promiseResults = [] + it('fails verification', (done) => { + let promiseResults = [] - const verificationPromise = + const verificationPromise = request.get(`${PROVIDER_URL}/projects`) .set({ 'Accept': 'application/json' }) .then((response) => { promiseResults.push(response) - return request.get(`${PROVIDER_URL}/projects/2`).set({ 'Accept': 'application/json' }) + return request.delete(`${PROVIDER_URL}/projects/2`) }) .then(() => {}, (err) => { promiseResults.push(err.response) }) .then(() => provider.verify(promiseResults)) - expect(verificationPromise).to.eventually.eql([JSON.stringify(EXPECTED_BODY), '']).notify(done) - }) - }) - - context('with an unexpected interaction', () => { - // add interactions, as many as needed - beforeEach((done) => { - provider.addInteraction({ - state: 'i have a list of projects', - uponReceiving: 'a request for projects', - withRequest: { - method: 'get', - path: '/projects', - headers: { 'Accept': 'application/json' } - }, - willRespondWith: { - status: 200, - headers: { 'Content-Type': 'application/json' }, - body: EXPECTED_BODY - } - }).then(() => done()) - }) - - // once test is run, write pact and remove interactions - afterEach((done) => { - provider.finalize().then(() => done()) - }) - - it('fails verification', (done) => { - let promiseResults = [] - - const verificationPromise = - request.get(`${PROVIDER_URL}/projects`) - .set({ 'Accept': 'application/json' }) - .then((response) => { - promiseResults.push(response) - return request.delete(`${PROVIDER_URL}/projects/2`) - }) - .then(() => {}, (err) => { promiseResults.push(err.response) }) - .then(() => provider.verify(promiseResults)) - - expect(verificationPromise).to.be.rejectedWith('No interaction found for DELETE /projects/2').notify(done) + expect(verificationPromise).to.be.rejectedWith('Error: Pact verification failed - expected interactions did not match actual.').notify(done) + }) }) }) - }) - - }) - }) diff --git a/test/pact.spec.js b/test/pact.spec.js index c901478d3..3f4c6d839 100644 --- a/test/pact.spec.js +++ b/test/pact.spec.js @@ -19,11 +19,11 @@ describe('Pact', () => { }) it('throws Error when consumer not informed', () => { - expect(() => { Pact({}) }).to.throw(Error, 'You must inform a Consumer for this Pact.') + expect(() => { Pact({}) }).to.throw(Error, 'You must specify a Consumer for this pact.') }) - it('throws Error when provider not informed', () => { - expect(() => { Pact({ consumer: 'abc' }) }).to.throw(Error, 'You must inform a Provider for this Pact.') + it('throws Error when provider not specified', () => { + expect(() => { Pact({ consumer: 'abc' }) }).to.throw(Error, 'You must specify a Provider for this pact.') }) it('returns object with three functions to be invoked', (done) => { @@ -36,7 +36,7 @@ describe('Pact', () => { done() }) - it('creates mockSerive with custom ip and port', (done) => { + it('creates mockService with custom ip and port', (done) => { let pact = Pact({ consumer: 'A', provider: 'B', host: '192.168.10.1', port: 8443, ssl: true }) expect(pact).to.have.property('addInteraction') expect(pact).to.have.property('verify') @@ -50,6 +50,7 @@ describe('Pact', () => { describe('#addInteraction', () => { let pact, Pact + let port = 4567 beforeEach(() => { Pact = proxyquire('../src/pact', { @@ -57,7 +58,7 @@ describe('Pact', () => { return { addInteraction: (int) => Promise.resolve(int.json()) } } }) - pact = Pact({ consumer: 'A', provider: 'B' }) + pact = Pact({ consumer: 'A', provider: 'B', port: port++ }) }) it('creates interaction with state', (done) => {