├── .babelrc ├── .eslintrc ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── karma.config.js ├── package-lock.json ├── package.json ├── src ├── client.js └── client_spec.js ├── test └── helpers.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env", 4 | ], 5 | } 6 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": true, 5 | "node": true, 6 | "jasmine": true, 7 | "es6": true 8 | }, 9 | "rules": { 10 | "strict": 0, 11 | "quotes": [ 12 | 2, 13 | "single" 14 | ] 15 | }, 16 | "globals": { 17 | "sinon": false, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | travis.log 4 | npm-debug.log 5 | *.swp 6 | 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '8' 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | =============================================================================== 3 | 4 | Have something to add? Contributions are enormously welcome! 5 | 6 | 1. Fork this repo 7 | 2. Update the spec and implement the change 8 | 3. Submit a [pull request](help.github.com/pull-requests/) 9 | 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | Copyright (C) RJ Zaworski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Demo: Testing window.fetch 2 | =============================================================================== 3 | 4 | [![Build Status](https://travis-ci.org/rjz/testing-window-fetch.svg?branch=master)](https://travis-ci.org/rjz/testing-window-fetch) 5 | 6 | Demo project for [testing API requests from `window.fetch`][article]. 7 | 8 | Installation 9 | ------------------------------------------------------------------------------- 10 | 11 | Install and run tests: 12 | 13 | $ npm install 14 | $ npm test 15 | 16 | License 17 | ------------------------------------------------------------------------------- 18 | 19 | MIT 20 | 21 | [article]: https://rjzaworski.com/2015/06/testing-api-requests-from-window-fetch 22 | -------------------------------------------------------------------------------- /karma.config.js: -------------------------------------------------------------------------------- 1 | const webpackConfig = require('./webpack.config'); 2 | 3 | module.exports = function (config) { 4 | config.set({ 5 | autoWatch: true, 6 | browsers: ['PhantomJS'], 7 | files: [ 8 | 'src/*_spec.js' 9 | ], 10 | frameworks: [ 11 | 'jasmine', 12 | 'sinon', 13 | ], 14 | preprocessors: { 15 | 'src/*.js': ['webpack', 'sourcemap'], 16 | }, 17 | webpack: webpackConfig, 18 | webpackServer: { 19 | noInfo: true 20 | } 21 | }); 22 | }; 23 | 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "testing-window-fetch", 3 | "version": "0.0.1", 4 | "description": "Testing example for window.fetch", 5 | "main": "src/main.js", 6 | "scripts": { 7 | "pretest": "eslint ./src", 8 | "test": "karma start ./karma.config.js --single-run", 9 | "build": "webpack" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/rjz/testing-window-fetch" 14 | }, 15 | "keywords": [], 16 | "author": "RJ Zaworski ", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/rjz/testing-window-fetch/issues" 20 | }, 21 | "devDependencies": { 22 | "babel-core": "^6.26.0", 23 | "babel-eslint": "^7.2.3", 24 | "babel-loader": "^7.1.1", 25 | "babel-polyfill": "^6.26.0", 26 | "babel-preset-env": "^1.6.0", 27 | "eslint": "^0.20.0", 28 | "jasmine": "^2.3.1", 29 | "jasmine-core": "^2.3.0", 30 | "karma": "^1.7.1", 31 | "karma-jasmine": "^0.3.8", 32 | "karma-phantomjs-launcher": "^1.0.4", 33 | "karma-sinon": "^1.0.5", 34 | "karma-sourcemap-loader": "^0.3.7", 35 | "karma-webpack": "^2.0.4", 36 | "sinon": "^1.14.1", 37 | "webpack": "^3.5.5" 38 | }, 39 | "dependencies": { 40 | "whatwg-fetch": "^0.9.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/client.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | import 'whatwg-fetch'; 3 | 4 | function apiError (status, message) { 5 | var err = new Error(message); 6 | err.status = status; 7 | return err; 8 | } 9 | 10 | function filterAPIError (res) { 11 | if (res.status > 399) { 12 | return res.json().then(function (json) { 13 | throw apiError(res.status, json.message); 14 | }); 15 | } 16 | 17 | return res.json(); 18 | } 19 | 20 | function onAPIResponse (json) { 21 | return { 22 | hello: json.hello.toUpperCase() 23 | }; 24 | } 25 | 26 | export default function client(path) { 27 | return window.fetch(path) 28 | .then(filterAPIError) 29 | .then(onAPIResponse); 30 | } 31 | -------------------------------------------------------------------------------- /src/client_spec.js: -------------------------------------------------------------------------------- 1 | import 'whatwg-fetch'; 2 | 3 | import client from './client'; 4 | import { jsonError, jsonOk } from '../test/helpers'; 5 | 6 | describe('.fetch', () => { 7 | 8 | beforeEach(() => { 9 | sinon.stub(window, 'fetch'); 10 | }); 11 | 12 | afterEach(() => { 13 | window.fetch.restore(); 14 | }); 15 | 16 | describe('making a request via the client', () => { 17 | beforeEach(() => { 18 | window.fetch.returns(Promise.reject(new Error('Just a stub'))); 19 | }); 20 | 21 | it('behaves like any other sinon spy', () => 22 | client('/foobar') 23 | .catch(() => expect(window.fetch.firstCall.args[0]).toBe('/foobar'))); 24 | }); 25 | 26 | describe('stubbing response', () => { 27 | 28 | beforeEach(() => { 29 | const res = new window.Response('{"hello":"world"}', { 30 | status: 200, 31 | headers: { 32 | 'Content-type': 'application/json' 33 | } 34 | }); 35 | 36 | window.fetch.returns(Promise.resolve(res)); 37 | }); 38 | 39 | it('formats response correctly', () => 40 | client('/foobar') 41 | .then((json) => { 42 | expect(json.hello).toBe('WORLD'); 43 | })); 44 | }); 45 | 46 | describe('stubbing response (uses test helpers)', () => { 47 | 48 | beforeEach(() => { 49 | window.fetch.returns(jsonOk({ 50 | hello: 'world' 51 | })); 52 | }); 53 | 54 | it('formats response correctly', () => 55 | client('/foobar') 56 | .then((json) => { 57 | expect(json.hello).toBe('WORLD'); 58 | })); 59 | }); 60 | 61 | describe('error response (uses test helpers)', () => { 62 | 63 | beforeEach(() => { 64 | window.fetch.returns(jsonError(401, { 65 | message: 'authentication required' 66 | })); 67 | }); 68 | 69 | it('returns correct body', () => 70 | client('/error-route') 71 | .catch(({ status, message }) => { 72 | expect(status).toBe(401); 73 | expect(message).toBe('authentication required'); 74 | })); 75 | }); 76 | }); 77 | 78 | -------------------------------------------------------------------------------- /test/helpers.js: -------------------------------------------------------------------------------- 1 | require('whatwg-fetch'); 2 | 3 | export function jsonOk (body) { 4 | var mockResponse = new window.Response(JSON.stringify(body), { 5 | status: 200, 6 | headers: { 7 | 'Content-type': 'application/json' 8 | } 9 | }); 10 | 11 | return Promise.resolve(mockResponse); 12 | } 13 | 14 | export function jsonError (status, body) { 15 | var mockResponse = new window.Response(JSON.stringify(body), { 16 | status: status, 17 | headers: { 18 | 'Content-type': 'application/json' 19 | } 20 | }); 21 | 22 | return Promise.resolve(mockResponse); 23 | } 24 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { name } = require('./package.json'); 3 | 4 | module.exports = { 5 | devtool: 'inline-source-map', 6 | module: { 7 | rules: [ 8 | { 9 | test: /\.js/, 10 | exclude: /node_modules/, 11 | loader: 'babel-loader', 12 | }, 13 | ], 14 | }, 15 | resolve: { 16 | extensions: ['.js'], 17 | modules: ['node_modules'] 18 | }, 19 | }; 20 | --------------------------------------------------------------------------------