From 2bdf1907c9f582bb5b1742a657ea7dde41ff692c Mon Sep 17 00:00:00 2001 From: Maxime Bertonnier Date: Sat, 30 Mar 2019 18:12:01 +0100 Subject: [PATCH 01/11] chore(package): add testing tools and scripts --- package.json | 11 +++++++++-- tsconfig.json | 7 ++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 01e303e..faeadef 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "types": "./src/index.d.ts", "scripts": { "start": "tsc --watch", - "test": "echo \"Error: no test specified\" && exit 1" + "build": "tsc", + "test": "jest --watch --verbose" }, "repository": { "type": "git", @@ -37,7 +38,13 @@ }, "homepage": "https://github.com/Exilz/apipeline#readme", "devDependencies": { - "typescript": "^2.4.2" + "@babel/core": "^7.4.0", + "@babel/preset-env": "^7.4.2", + "@types/jest": "^24.0.11", + "babel-jest": "^24.5.0", + "jest": "^24.5.0", + "typescript": "^2.4.2", + "whatwg-fetch": "^3.0.0" }, "dependencies": { "jssha": "^2.3.1", diff --git a/tsconfig.json b/tsconfig.json index a71bb14..6817270 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "target": "es5", "baseUrl": "./", - "lib": ["es2015"], + "lib": ["es2015", "dom"], "pretty": true, "jsx": "react-native", "outDir": "./dist", @@ -20,6 +20,7 @@ ], "exclude": [ "node_modules", - "demo" + "demo", + "__tests__" ] -} \ No newline at end of file +} From df7da481ac645570e21371d32709db29863f315f Mon Sep 17 00:00:00 2001 From: Maxime Bertonnier Date: Sat, 30 Mar 2019 18:14:14 +0100 Subject: [PATCH 02/11] WIP test(requirements): start writing functional tests --- __tests__/requirements.test.js | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 __tests__/requirements.test.js diff --git a/__tests__/requirements.test.js b/__tests__/requirements.test.js new file mode 100644 index 0000000..7ce6dcd --- /dev/null +++ b/__tests__/requirements.test.js @@ -0,0 +1,7 @@ +import 'whatwg-fetch'; + +describe('>>> Requirements', () => { + it('Fetch is polyfilled', () => { + expect(window.fetch).toBeDefined(); + }); +}); From 253dd83d4404e6facfc3bc10429c3236a9fbd91b Mon Sep 17 00:00:00 2001 From: Maxime Bertonnier Date: Sat, 30 Mar 2019 18:14:31 +0100 Subject: [PATCH 03/11] WIP test(instantiation): start writing functional tests --- __tests__/instantiation.test.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 __tests__/instantiation.test.js diff --git a/__tests__/instantiation.test.js b/__tests__/instantiation.test.js new file mode 100644 index 0000000..143522d --- /dev/null +++ b/__tests__/instantiation.test.js @@ -0,0 +1,33 @@ +import APIpeline from '../dist'; + +const API_OPTIONS = { + fetchMethod: require('whatwg-fetch'), + domains: { default: 'http://default.tld' } +}; + +const API_SERVICES = { + example: { path: 'example' } +}; + +describe('>>> Instantiation', () => { + + it('Success with options and services', () => { + new APIpeline(API_OPTIONS, API_SERVICES); + }); + + it('Success without services', () => { + new APIpeline(API_OPTIONS); + }); + + it('Fails when fetchMethod is missing', () => { + expect(() => { + new APIpeline({ ...API_OPTIONS, fetchMethod: undefined }, API_SERVICES); + }).toThrow(/^Your fetch method is undefined/); + }); + + it('Fails when default domain is missing', () => { + expect(() => { + new APIpeline({ ...API_OPTIONS, domains: { staging: 'http://staging.myapi.tld' } }, API_SERVICES); + }).toThrow(/^You didn't set your default domain URL in your options/); + }); +}); From ef1ccc73a59b46e1f2fd64467e1650d9d04ddf1c Mon Sep 17 00:00:00 2001 From: Maxime Bertonnier Date: Sat, 30 Mar 2019 18:14:45 +0100 Subject: [PATCH 04/11] WIP test(setup): start writing functional tests --- __tests__/setup.test.js | 66 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 __tests__/setup.test.js diff --git a/__tests__/setup.test.js b/__tests__/setup.test.js new file mode 100644 index 0000000..9950d8a --- /dev/null +++ b/__tests__/setup.test.js @@ -0,0 +1,66 @@ +import APIpeline, { DEFAULT_API_OPTIONS, DEFAULT_SERVICE_OPTIONS } from '../dist'; + +const API_OPTIONS = { + fetchMethod: require('whatwg-fetch'), + domains: { default: 'http://default.tld' } +}; + +describe('>>> Setup API options and services', () => { + + it('Merges the default API options for missing keys', () => { + const api = new APIpeline(API_OPTIONS); + expect(api._APIOptions).toStrictEqual({ + ...DEFAULT_API_OPTIONS, + ...API_OPTIONS, + }); + }); + + it('Merges the default services options for each service', () => { + const SERVICES = { default: { path: '/' }, simpleService: { path: 'simpleService', method: 'POST' } }; + const api = new APIpeline(API_OPTIONS, SERVICES); + expect(api._APIServices).toStrictEqual({ + default: { ...DEFAULT_SERVICE_OPTIONS, ...SERVICES.default, }, + simpleService: { ...DEFAULT_SERVICE_OPTIONS, ...SERVICES.simpleService }, + }); + }); + + it('Updates API options after instantiation', () => { + const api = new APIpeline(API_OPTIONS); + const UPDATED_OPTIONS = { printNetworkRequests: true }; + api.setOptions(UPDATED_OPTIONS); + expect(api._APIOptions).toStrictEqual({ + ...DEFAULT_API_OPTIONS, + ...API_OPTIONS, + ...UPDATED_OPTIONS + }); + }); + + it('Sets up services after instantiation without pre-defined services', () => { + const UPDATED_SERVICES = { newService: { path: 'http://myNewService.tld' } }; + const api = new APIpeline(API_OPTIONS); + api.setServices(UPDATED_SERVICES); + expect(api._APIServices).toStrictEqual({ + newService: { + ...UPDATED_SERVICES.newService, + ...DEFAULT_SERVICE_OPTIONS + } + }); + }); + + it('Adds a new service after instantiation with pre-defined services', () => { + const PRE_DEF_SERVICES = { default: { path: 'http://default.tld' } }; + const UPDATED_SERVICES = { newService: 'http://myNewService.tld' }; + const api = new APIpeline(API_OPTIONS, PRE_DEF_SERVICES); + api.setServices(UPDATED_SERVICES); + expect(api._APIServices).toStrictEqual({ + default: { + ...PRE_DEF_SERVICES.default, + ...DEFAULT_SERVICE_OPTIONS + }, + newService: { + ...UPDATED_SERVICES.newService, + ...DEFAULT_SERVICE_OPTIONS + } + }); + }); +}); From 60e0d244c3f033db9a71d11f7ce67740e3cb25ca Mon Sep 17 00:00:00 2001 From: Maxime Bertonnier Date: Sun, 31 Mar 2019 01:13:21 +0100 Subject: [PATCH 05/11] test(unit): add first unit test, improve requirement check --- __tests__/requirements.test.js | 9 +++++++-- __tests__/unit.test.js | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 __tests__/unit.test.js diff --git a/__tests__/requirements.test.js b/__tests__/requirements.test.js index 7ce6dcd..0badd0d 100644 --- a/__tests__/requirements.test.js +++ b/__tests__/requirements.test.js @@ -1,7 +1,12 @@ -import 'whatwg-fetch'; +import APIpeline from '../dist/index'; + +const API_OPTIONS = { + fetchMethod: require('whatwg-fetch'), + domains: { default: 'default' } +}; describe('>>> Requirements', () => { it('Fetch is polyfilled', () => { - expect(window.fetch).toBeDefined(); + new APIpeline(API_OPTIONS); }); }); diff --git a/__tests__/unit.test.js b/__tests__/unit.test.js new file mode 100644 index 0000000..21aa83a --- /dev/null +++ b/__tests__/unit.test.js @@ -0,0 +1,17 @@ +import APIpeline, { HTTP_METHODS } from '../dist'; + +const API_OPTIONS = { + fetchMethod: require('whatwg-fetch'), + domains: { default: 'http://default.tld' } +}; + + +describe('>>> Unit tests', () => { + + it('Implements every HTTP methods', () => { + const api = new APIpeline(API_OPTIONS); + HTTP_METHODS.forEach((method) => { + expect(typeof api[method.toLowerCase()]).toBe('function'); + }); + }) +}); From c28778ae26acb6a567cef11f0650e869fc87c75a Mon Sep 17 00:00:00 2001 From: Maxime Bertonnier Date: Sun, 31 Mar 2019 01:14:29 +0100 Subject: [PATCH 06/11] chore(package): add express to mock API & configure jest --- package.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package.json b/package.json index faeadef..a817a22 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,9 @@ "build": "tsc", "test": "jest --watch --verbose" }, + "jest": { + "testRegex": "(\\/__tests__\\/.*|(\\.|\\/)(test|spec))\\.(test)\\.[jt]sx?$" + }, "repository": { "type": "git", "url": "git+https://github.com/Exilz/apipeline.git" @@ -42,6 +45,7 @@ "@babel/preset-env": "^7.4.2", "@types/jest": "^24.0.11", "babel-jest": "^24.5.0", + "express": "^4.16.4", "jest": "^24.5.0", "typescript": "^2.4.2", "whatwg-fetch": "^3.0.0" From 23612aae4ad6eae5e8b9e2515c495142e3131e14 Mon Sep 17 00:00:00 2001 From: Maxime Bertonnier Date: Sun, 31 Mar 2019 01:15:35 +0100 Subject: [PATCH 07/11] chore(api): configure an express server to serve static data for testing --- __tests__/server.js | 12 ++++++++++++ __tests__/static/example.json | 3 +++ 2 files changed, 15 insertions(+) create mode 100644 __tests__/server.js create mode 100644 __tests__/static/example.json diff --git a/__tests__/server.js b/__tests__/server.js new file mode 100644 index 0000000..df98583 --- /dev/null +++ b/__tests__/server.js @@ -0,0 +1,12 @@ +import express from 'express'; + +const app = express(); +const appRouter = express.Router(); +app.use((req, res, next) => { + res.header('Access-Control-Allow-Origin', '*'); + next(); +}); +appRouter.use('/', express.static('__tests__/static')); +app.use(appRouter); + +export default app; diff --git a/__tests__/static/example.json b/__tests__/static/example.json new file mode 100644 index 0000000..77ae496 --- /dev/null +++ b/__tests__/static/example.json @@ -0,0 +1,3 @@ +{ + "test": "ok" +} From 8e19ecd5355253d22a8624b09050928d70a4ac47 Mon Sep 17 00:00:00 2001 From: Maxime Bertonnier Date: Sun, 31 Mar 2019 01:16:03 +0100 Subject: [PATCH 08/11] WIP test(api): test API calls --- __tests__/api.test.js | 48 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 __tests__/api.test.js diff --git a/__tests__/api.test.js b/__tests__/api.test.js new file mode 100644 index 0000000..f5fcbad --- /dev/null +++ b/__tests__/api.test.js @@ -0,0 +1,48 @@ +import APIpeline from '../dist/index'; +import 'whatwg-fetch'; +import exampleData from './static/example'; +import server from './server'; + +const API_OPTIONS = { + fetchMethod: window.fetch, + domains: { default: 'http://127.0.0.1:23135' } +}; + +const API_SERVICES = { + example: { path: 'example.json' } +}; + + +describe('>>> Test API calls', () => { + let mockedServer; + beforeAll((done) => { + mockedServer = server.listen(23135, done); + }); + + afterAll((done) => { + mockedServer.close(done); + }); + + it('GET/example.json (api.get)', async () => { + const api = new APIpeline(API_OPTIONS, API_SERVICES); + await expect(api.get('example')).resolves.toEqual(exampleData); + }); + + it('GET/example.json (api.fetch)', async () => { + const api = new APIpeline(API_OPTIONS, API_SERVICES); + await expect(api.fetch('example')).resolves.toEqual(exampleData); + }); + + it.only('GET/example.json (api.getHeaders)', async () => { + const api = new APIpeline(API_OPTIONS, API_SERVICES); + expect.assertions(2); + try { + const headers = await api.fetchHeaders('example'); + expect(headers).toHaveProperty('content-type'); + expect(headers['content-type']).toEqual('application/json; charset=UTF-8'); + } catch (err) { + throw err; + } + }); +}); + From cf10bd4e572eff6ccb003388cd4f4b2eaf710ad5 Mon Sep 17 00:00:00 2001 From: Maxime Bertonnier Date: Sun, 31 Mar 2019 01:28:41 +0100 Subject: [PATCH 09/11] chore(jest): configure code coverage reporting command --- __tests__/api.test.js | 2 +- package.json | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/__tests__/api.test.js b/__tests__/api.test.js index f5fcbad..ad2b003 100644 --- a/__tests__/api.test.js +++ b/__tests__/api.test.js @@ -33,7 +33,7 @@ describe('>>> Test API calls', () => { await expect(api.fetch('example')).resolves.toEqual(exampleData); }); - it.only('GET/example.json (api.getHeaders)', async () => { + it('GET/example.json (api.getHeaders)', async () => { const api = new APIpeline(API_OPTIONS, API_SERVICES); expect.assertions(2); try { diff --git a/package.json b/package.json index a817a22..f8d4f59 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,12 @@ "scripts": { "start": "tsc --watch", "build": "tsc", - "test": "jest --watch --verbose" + "test": "jest --watch --verbose", + "test:coverage": "jest --collectCoverage" }, "jest": { - "testRegex": "(\\/__tests__\\/.*|(\\.|\\/)(test|spec))\\.(test)\\.[jt]sx?$" + "testRegex": "(\\/__tests__\\/.*|(\\.|\\/)(test|spec))\\.(test)\\.[jt]sx?$", + "collectCoverageFrom": ["dist/**/*.js"] }, "repository": { "type": "git", From bc4db96ce15bb7aa8bb91ac1c0555f5f50357c33 Mon Sep 17 00:00:00 2001 From: Maxime Bertonnier Date: Sat, 20 Apr 2019 16:53:42 +0200 Subject: [PATCH 10/11] test(api): configure mocked server to test POST & headers --- __tests__/server.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/__tests__/server.js b/__tests__/server.js index df98583..8627a3a 100644 --- a/__tests__/server.js +++ b/__tests__/server.js @@ -1,12 +1,19 @@ -import express from 'express'; +const express = require('express'); +const bodyParser = require('body-parser'); const app = express(); const appRouter = express.Router(); app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); + res.header('Access-Control-Allow-Headers', '*'); + res.header('Access-Control-Request-Headers', '*'); next(); }); appRouter.use('/', express.static('__tests__/static')); app.use(appRouter); +appRouter.use(bodyParser.urlencoded({ extended: true })); + +appRouter.get('/testHeaders', async (req, res) => res.json(req.headers)); +appRouter.post('/postExample', async (req, res) => res.json(req.body)); export default app; From 3b18bced02796d26b91d5579969b019521a2a0de Mon Sep 17 00:00:00 2001 From: Maxime Bertonnier Date: Sat, 20 Apr 2019 16:54:32 +0200 Subject: [PATCH 11/11] test(api): add headers & POST tests --- __tests__/api.test.js | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/__tests__/api.test.js b/__tests__/api.test.js index ad2b003..e7d1a65 100644 --- a/__tests__/api.test.js +++ b/__tests__/api.test.js @@ -9,7 +9,9 @@ const API_OPTIONS = { }; const API_SERVICES = { - example: { path: 'example.json' } + testHeaders: { path: 'testHeaders' }, + getExample: { path: 'example.json' }, + postExample: { path: 'postExample', method: 'POST' } }; @@ -23,26 +25,51 @@ describe('>>> Test API calls', () => { mockedServer.close(done); }); + it('Returns headers', async () => { + const api = new APIpeline(API_OPTIONS, API_SERVICES); + try { + await expect( + api.get('testHeaders', { headers: { 'Content-Type': 'application/pdf' } }) + ).resolves.toHaveProperty('content-type', 'application/pdf'); + } catch (err) { + throw err; + } + }); + it('GET/example.json (api.get)', async () => { const api = new APIpeline(API_OPTIONS, API_SERVICES); - await expect(api.get('example')).resolves.toEqual(exampleData); + await expect(api.get('getExample')).resolves.toEqual(exampleData); }); it('GET/example.json (api.fetch)', async () => { const api = new APIpeline(API_OPTIONS, API_SERVICES); - await expect(api.fetch('example')).resolves.toEqual(exampleData); + await expect(api.fetch('getExample')).resolves.toEqual(exampleData); }); it('GET/example.json (api.getHeaders)', async () => { const api = new APIpeline(API_OPTIONS, API_SERVICES); expect.assertions(2); try { - const headers = await api.fetchHeaders('example'); + const headers = await api.fetchHeaders('getExample'); expect(headers).toHaveProperty('content-type'); expect(headers['content-type']).toEqual('application/json; charset=UTF-8'); } catch (err) { throw err; } }); -}); + it('POST/postExample (api.post)', async () => { + const api = new APIpeline(API_OPTIONS, API_SERVICES); + try { + await expect(api.post( + 'postExample', + { + fetchOptions: { body: 'key=value' }, + headers: { 'Content-Type': 'application/x-www-form-urlencoded' } + } + )).resolves.toEqual({ key: 'value' }); + } catch (err) { + throw err; + } + }); +});