├── .babelrc ├── .eslintignore ├── .eslintrc ├── .flowconfig ├── .gitignore ├── .npmignore ├── README.md ├── package.json ├── src ├── api │ ├── api_key.js │ ├── get_single_mp_service.js │ └── list_mps_service.js └── objects │ ├── mp.js │ └── office.js ├── test ├── .eslintrc └── acceptance │ ├── get_mp.spec.js │ └── list_all_MPs.spec.js └── test_data └── mps ├── .gitignore ├── helen_hayes.json └── helen_hayes_short.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "stage-0" 5 | ], 6 | "plugins": [ 7 | "syntax-async-functions", 8 | "transform-async-functions", 9 | "transform-flow-strip-types", 10 | "transform-runtime" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | declarations 4 | shared 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | parser: babel-eslint 2 | extends: airbnb/base 3 | env: 4 | node: true 5 | globals: 6 | describe: true 7 | beforeEach: true 8 | before: true 9 | afterEach: true 10 | after: true 11 | it: true 12 | logger: true 13 | plugins: 14 | - babel 15 | - import 16 | rules: 17 | max-len: 18 | - warn 19 | - 150 20 | prefer-arrow-callback: off 21 | indent: 22 | - error 23 | - 4 24 | comma-dangle: 25 | - error 26 | - never 27 | no-duplicate-imports: off 28 | require-yield: off 29 | generator-star-spacing: off 30 | babel/generator-star-spacing: error 31 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/node_modules/.*babel.* 3 | .*/node_modules/.*/test/.* 4 | .*/node_modules/flow-copy-source/.* 5 | 6 | [include] 7 | 8 | [libs] 9 | declarations 10 | 11 | [options] 12 | esproposal.class_static_fields=enable 13 | module.system.node.resolve_dirname=node_modules 14 | module.system.node.resolve_dirname=shared 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea 3 | node_modules 4 | bin/ 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | test 3 | .idea 4 | .babelrc 5 | .eslintignore 6 | .eslintrc 7 | .flowconfig 8 | .npmignore 9 | Jenkinsfile 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Example Mocha / Chai Javascript test framework 2 | 3 | An example test framework to get started with API testing using Javascript. Uses the TheyWorkForYou.com API as an example. 4 | 5 | ## Tech stack 6 | 7 | - Javascript 8 | - Mocha: Node.js based testing framework 9 | - Chai: assertion library 10 | - Axios: promise-based async http client 11 | - Flow: static type checker 12 | 13 | ## How to get started 14 | 15 | - Get an API key from https://www.theyworkforyou.com/api/key - you'll have to sign up for an account 16 | - Export the API key to your environment: `export TWFY_KEY=...` 17 | - Clone the project and cd into the project directory 18 | - Download dependencies: `npm build` 19 | 20 | Now you can just run the tests: `npm test` 21 | 22 | All done! -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Matt Long", 3 | "name": "js-api-testing-quickstart", 4 | "version": "0.0.1", 5 | "private": false, 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/burythehammer/js-api-testing-quickstart" 9 | }, 10 | "scripts": { 11 | "build": "babel src --out-dir lib && flow-copy-source -v src lib", 12 | "lint": "eslint .", 13 | "typecheck": "flow", 14 | "test": "mocha --compilers js:babel-register test/**/*.js", 15 | "test-debug": "mocha --compilers js:babel-register test/**/*.js --grep @debug", 16 | "clean": "rm -rf lib node_modules" 17 | }, 18 | "bin": { 19 | "session": "bin/session.js" 20 | }, 21 | "dependencies": { 22 | "aws-sdk": "2.6.15", 23 | "aws-sdk-flow-decls": "1.2.1", 24 | "config": "^1.24.0", 25 | "shortid": "2.2.4", 26 | "uuid": "3.0.1", 27 | "underscore.deep": "^0.5.1", 28 | "casual": "1.5.8", 29 | "axios": "~0.15.3" 30 | }, 31 | "devDependencies": { 32 | "babel-cli": "^6.6.5", 33 | "babel-eslint": "^6.1.0", 34 | "babel-plugin-syntax-async-functions": "^6.5.0", 35 | "babel-plugin-transform-async-functions": "^6.5.0", 36 | "babel-plugin-transform-async-to-generator": "^6.7.4", 37 | "babel-plugin-transform-class-properties": "^6.11.5", 38 | "babel-plugin-transform-flow-strip-types": "^6.7.0", 39 | "babel-plugin-transform-runtime": "^6.5.2", 40 | "babel-preset-es2015": "^6.6.0", 41 | "babel-preset-stage-0": "^6.5.0", 42 | "babel-register": "^6.5.2", 43 | "babel-runtime": "^6.6.1", 44 | "chai": "^3.5.0", 45 | "chai-as-promised": "^5.2.0", 46 | "chai-subset": "^1.2.2", 47 | "eslint": "^3.2.2", 48 | "eslint-config-airbnb": "^10.0.0", 49 | "eslint-plugin-babel": "^3.3.0", 50 | "eslint-plugin-import": "^1.16.0", 51 | "eslint-plugin-react": "^6.0.0", 52 | "flow-bin": "^0.33.0", 53 | "flow-copy-source": "^1.0.0", 54 | "mocha": "^3.2.0", 55 | "should": "^11.1.1" 56 | }, 57 | "license": "LicenseRef-LICENSE" 58 | } 59 | -------------------------------------------------------------------------------- /src/api/api_key.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | export default function getApiKey(): string { 4 | const apiKeyEnv = process.env.TWFY_KEY; 5 | if (!apiKeyEnv) { throw new Error('They work for you API key not set'); } 6 | return apiKeyEnv; 7 | } 8 | -------------------------------------------------------------------------------- /src/api/get_single_mp_service.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Axios from 'axios'; 4 | import getApiKey from './api_key'; 5 | import type { MP } from '../objects/mp'; 6 | 7 | export default class GetMpService { 8 | axios: Axios; 9 | apiKey: string; 10 | 11 | constructor() { 12 | this.apiKey = getApiKey(); 13 | this.axios = Axios.create({ 14 | baseURL: 'https://www.theyworkforyou.com/api' 15 | }); 16 | } 17 | 18 | async getMP(mpId: string): Promise { 19 | const response = await this.axios.get(`/getMP?id=${mpId}&key=${this.apiKey}&output=js`); 20 | return response.data[0]; 21 | } 22 | 23 | async getMPbyConstituency(constituency: string): Promise { 24 | const response = await this.axios.get(`/getMP?constituency=${constituency}&key=${this.apiKey}&output=js`); 25 | return response.data; 26 | } 27 | 28 | async getMPbyPostcode(postcode: string): Promise { 29 | const response = await this.axios.get(`/getMP?postcode=${postcode}&key=${this.apiKey}&output=js`); 30 | return response.data; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/api/list_mps_service.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Axios from 'axios'; 4 | import getApiKey from './api_key'; 5 | import type { MP } from '../objects/mp'; 6 | 7 | export type MpQuery = { 8 | date: string; 9 | party: string; 10 | search: string; 11 | } 12 | 13 | export function MpQueryBuilder() { 14 | 15 | let query = {}; 16 | 17 | this.date = function (date: string) { 18 | query.date = date; 19 | return this; 20 | }; 21 | 22 | this.party = function (party: string) { 23 | query.party = party; 24 | return this; 25 | 26 | }; 27 | 28 | this.search = function (search: string) { 29 | query.search = search; 30 | return this; 31 | 32 | }; 33 | 34 | this.build = function() { 35 | return query; 36 | }; 37 | } 38 | 39 | export default class ListMpService { 40 | axios: Axios; 41 | apiKey: string; 42 | 43 | constructor() { 44 | this.apiKey = getApiKey(); 45 | this.axios = Axios.create({ 46 | baseURL: 'https://www.theyworkforyou.com/api' 47 | }); 48 | } 49 | 50 | async listMPs(query: MpQuery): Promise { 51 | const response = await this.axios.get(`/getMPs?date=${query.date}&party=${query.party}&search=${query.search}&key=${this.apiKey}`); 52 | return response.data; 53 | } 54 | 55 | async listAllMPs(): Promise { 56 | const response = await this.axios.get(`/getMPs?key=${this.apiKey}`); 57 | return response.data; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/objects/mp.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { Office } from './office'; 3 | 4 | export type MP = { 5 | member_id?: string; 6 | person_id?: string; 7 | name?: string; 8 | party?: string; 9 | constituency?: string; 10 | office?: Office[]; 11 | } 12 | -------------------------------------------------------------------------------- /src/objects/office.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | export type Office = { 4 | dept?: string; 5 | position?: string; 6 | from_date?: string; 7 | to_date?: string; 8 | }; 9 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | parser: babel-eslint 2 | extends: airbnb/base 3 | env: 4 | node: true 5 | globals: 6 | describe: true 7 | beforeEach: true 8 | before: true 9 | afterEach: true 10 | after: true 11 | it: true 12 | logger: true 13 | plugins: 14 | - import 15 | rules: 16 | max-len: 17 | - warn 18 | - 150 19 | indent: 20 | - error 21 | - 4 22 | comma-dangle: 23 | - error 24 | - never 25 | no-unused-vars: off 26 | no-unused-expressions: off 27 | no-duplicate-imports: off 28 | import/no-extraneous-dependencies: off -------------------------------------------------------------------------------- /test/acceptance/get_mp.spec.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import { describe, it, before } from 'mocha'; 3 | import chai, { expect } from 'chai'; 4 | import chaiAsPromised from 'chai-as-promised'; 5 | import chaiSubset from 'chai-subset'; 6 | import GetMpService from '../../src/api/get_single_mp_service'; 7 | import type { MP } from '../../src/objects/mp'; 8 | import testMp from '../../test_data/mps/helen_hayes.json'; 9 | 10 | 11 | chai.use(chaiAsPromised); 12 | chai.use(chaiSubset); 13 | 14 | describe('Get single MP endpoint', function run() { 15 | this.timeout(2000); 16 | 17 | let mpApi: GetMpService; 18 | 19 | before(async () => { 20 | mpApi = new GetMpService(); 21 | }); 22 | 23 | it('gets an MP by person ID', async () => { 24 | const mp: MP = await mpApi.getMP(testMp.person_id); 25 | expect(mp).to.eql(testMp); 26 | }); 27 | 28 | it('gets a local MP by postcode', async () => { 29 | const mp: MP = await mpApi.getMPbyPostcode('SE218HY'); 30 | expect(mp).to.eql(testMp); 31 | }); 32 | 33 | it('gets a local MP by constituency', async () => { 34 | const mp: MP = await mpApi.getMPbyConstituency(testMp.constituency); 35 | expect(mp).to.eql(testMp); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/acceptance/list_all_MPs.spec.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import { describe, it, before } from 'mocha'; 3 | import chai, { expect } from 'chai'; 4 | import chaiAsPromised from 'chai-as-promised'; 5 | import chaiSubset from 'chai-subset'; 6 | import ListMPsService, { MpQueryBuilder } from '../../src/api/list_mps_service'; 7 | import type { MP } from '../../src/objects/mp'; 8 | import testMp from '../../test_data/mps/helen_hayes_short.json'; 9 | 10 | chai.use(chaiAsPromised); 11 | chai.use(chaiSubset); 12 | 13 | let mpApi: ListMPsService; 14 | 15 | 16 | describe('List all MPs endpoint', function () { 17 | this.timeout(2000); 18 | 19 | let allMPs: MP[]; 20 | 21 | before(async () => { 22 | mpApi = new ListMPsService(); 23 | allMPs = await mpApi.listAllMPs(); 24 | }); 25 | 26 | it('gets a list of the correct length', async () => { 27 | expect(allMPs).to.be.length(649); 28 | }); 29 | 30 | it('gets a list containing a specific MP', async () => { 31 | expect(allMPs.map(mp => mp.name)).to.contain(testMp.name); 32 | }); 33 | 34 | describe('Filtering by party', function () { 35 | let labourMPs: MP[]; 36 | 37 | before(async () => { 38 | labourMPs = await mpApi.listMPs(new MpQueryBuilder().party("Labour").build()); 39 | }); 40 | 41 | it('gets a list of Labour MPs of the correct length', async () => { 42 | expect(labourMPs).to.be.length(233); 43 | }); 44 | 45 | it('gets a list of Labour MPs containing a specific Labour MP', async () => { 46 | expect(labourMPs.map(mp => mp.name)).to.contain(testMp.name); 47 | }); 48 | }); 49 | }); 50 | 51 | -------------------------------------------------------------------------------- /test_data/mps/.gitignore: -------------------------------------------------------------------------------- 1 | all-mps.json -------------------------------------------------------------------------------- /test_data/mps/helen_hayes.json: -------------------------------------------------------------------------------- 1 | { 2 | "member_id": "40834", 3 | "house": "1", 4 | "constituency": "Dulwich and West Norwood", 5 | "party": "Labour", 6 | "entered_house": "2015-05-08", 7 | "left_house": "9999-12-31", 8 | "entered_reason": "general_election", 9 | "left_reason": "still_in_office", 10 | "person_id": "25310", 11 | "lastupdate": "2015-05-08 03:42:04", 12 | "title": "", 13 | "given_name": "Helen", 14 | "family_name": "Hayes", 15 | "full_name": "Helen Hayes", 16 | "url": "/mp/25310/helen_hayes/dulwich_and_west_norwood", 17 | "image": "/images/mpsL/25310.jpeg", 18 | "image_height": 118, 19 | "image_width": 89, 20 | "office": [ 21 | { 22 | "moffice_id": "uk.parliament.data/Member/4510/Committee/17", 23 | "dept": "Communities and Local Government Committee", 24 | "position": "Member", 25 | "from_date": "2015-07-13", 26 | "to_date": "9999-12-31", 27 | "person": "25310", 28 | "source": "" 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /test_data/mps/helen_hayes_short.json: -------------------------------------------------------------------------------- 1 | { 2 | "member_id": "40834", 3 | "person_id": "25310", 4 | "name": "Helen Hayes", 5 | "party": "Labour", 6 | "constituency": "Dulwich and West Norwood", 7 | "office": [ 8 | { 9 | "dept": "Communities and Local Government Committee", 10 | "position": "Member", 11 | "from_date": "2015-07-13", 12 | "to_date": "9999-12-31" 13 | } 14 | ] 15 | } --------------------------------------------------------------------------------