├── README.md ├── lib └── injector.js ├── package.json └── sample ├── index.html ├── injector.js ├── main.js └── test.js /README.md: -------------------------------------------------------------------------------- 1 | # Response Injection 2 | 3 | ## description 4 | 5 | PoC of mocking networks access from browser in testing. 6 | inject custom Response to XHR from Service Worker, 7 | and you can update mock data with only reload the window thread. 8 | 9 | 10 | ## how to use 11 | 12 | ```js 13 | Injector( 14 | // response mock data 15 | { 16 | '/success': { 17 | head: { 18 | status: 200, 19 | statusText: 'OK', 20 | headers: { 21 | 'Content-Type': 'application/json' 22 | } 23 | }, 24 | body: { 25 | id: 200, 26 | name: 'jxck', 27 | mail: 'jxck@example.com' 28 | } 29 | }, 30 | '/fail': { 31 | head: { 32 | status: 404, 33 | statusText: 'Not Found', 34 | headers: { 35 | 'Content-Type': 'application/json' 36 | } 37 | }, 38 | body: { 39 | message: 'user not found' 40 | } 41 | } 42 | }, 43 | 44 | // option 45 | { 46 | ignore: ['/', '/test.js', '/main.js'], // ignore injection (default []) 47 | scope: '.' // scope for register worker (default '/') 48 | } 49 | 50 | ).then( 51 | 52 | // write test as usual 53 | function test() { 54 | console.log('start test'); 55 | 56 | get('/success').then(function(res) { 57 | console.assert(res.id, 200); 58 | }).catch(function(err) { 59 | console.assert(false); 60 | }); 61 | 62 | get('/fail').then(function(res) { 63 | console.assert(false); 64 | }).catch(function(err) { 65 | console.assert(err.message, 'user not found'); 66 | }); 67 | 68 | // etc 69 | } 70 | 71 | ).catch(console.error.bind(console)); 72 | ``` 73 | 74 | ## Sample 75 | 76 | ```sh 77 | $ git clone ttps://github.com/Jxck/response-injection 78 | $ cd response-injection/sample 79 | $ python -m SimleHTTPServer 3000 # or like your own 80 | ``` 81 | 82 | and open http://localhost:3000/ 83 | 84 | ## TODO 85 | 86 | - write test 87 | - write more readme 88 | - add to bower 89 | - support more mock style 90 | - array of Response 91 | - support body condittion 92 | 93 | ## License 94 | 95 | The MIT License (MIT) 96 | Copyright (c) 2013 Jxck 97 | -------------------------------------------------------------------------------- /lib/injector.js: -------------------------------------------------------------------------------- 1 | if ('ServiceWorkerGlobalScope' in self && self instanceof ServiceWorkerGlobalScope) { 2 | self.skipWaiting().then(function() { 3 | console.warn('skipWaiting'); 4 | }); 5 | 6 | ['install' , 'activate' , 'beforeevicted' , 'evicted'].forEach(function(ev) { 7 | self.addEventListener(ev, function(e) { 8 | console.info(e); 9 | }); 10 | }); 11 | 12 | self.addEventListener('activate', function(e) { 13 | console.log('activate'); 14 | e.waitUntil(self.clients.claim()); 15 | }); 16 | 17 | self.addEventListener('message', function(message) { 18 | var data = JSON.parse(message.data); 19 | console.log('message from main', data); 20 | 21 | var mockdata = data.mockdata; 22 | var option = data.option; 23 | console.log('mockdata', mockdata); 24 | console.log('option', option); 25 | 26 | console.log('set listener using mock data'); 27 | 28 | self.onfetch = function(event) { 29 | 30 | function createResponse(path) { 31 | var head = mockdata[path].head; 32 | var body = JSON.stringify(mockdata[path].body); 33 | console.log(head, body); 34 | return new Response(body, head); 35 | } 36 | 37 | var path = (new URL(event.request.url)).pathname; 38 | console.log('fetch event for', path); 39 | 40 | if(option.ignore.indexOf(path) >= 0) { 41 | // ignore 42 | return; 43 | } 44 | 45 | var response = createResponse(path); 46 | console.log('mock response', response); 47 | 48 | event.respondWith(response); 49 | }; 50 | 51 | console.log('response to main after setting fetch listener'); 52 | message.ports[0].postMessage('done'); 53 | 54 | }); 55 | } 56 | 57 | if (typeof window !== 'undefined') { 58 | function Injector(mockdata, option) { 59 | console.log(mockdata, option); 60 | option = option || {}; 61 | option.scope = option.scope || '.'; 62 | option.ignore = option.ignore || []; 63 | 64 | return Promise.race([ 65 | navigator.serviceWorker.register('injector.js', { scope: option.scope }), 66 | navigator.serviceWorker.getRegistration(), 67 | ]).then(function(registoration) { 68 | console.log('service worker ready'); 69 | 70 | var msgchan = new MessageChannel(); 71 | 72 | var message = JSON.stringify({ 73 | mockdata: mockdata, 74 | option: option 75 | }); 76 | 77 | // console.log('send mock data to the worker'); 78 | if(navigator.serviceWorker.controller) { 79 | navigator.serviceWorker.controller.postMessage(message, [msgchan.port2]); 80 | } else { 81 | navigator.serviceWorker.addEventListener('controllerchange', function(registoration) { 82 | console.log('controllerchange', arguments); 83 | navigator.serviceWorker.controller.postMessage(message, [msgchan.port2]); 84 | }); 85 | } 86 | 87 | return new Promise(function(done, fail) { 88 | // addEventListener だと動かない? 89 | // msgchan.port1.addEventListener('message', function(event) { 90 | msgchan.port1.onmessage = function(event) { 91 | // console.log('response from worker', event); 92 | if(event.data === 'done') { 93 | console.log('ready to test'); 94 | done(); 95 | } else { 96 | fail(new Error('event.data')); 97 | } 98 | }; 99 | }); 100 | }) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "response-injection", 3 | "version": "0.0.0", 4 | "description": "response injection for network access test using service worker", 5 | "main": "lib/injection.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/Jxck/response-injection.git" 12 | }, 13 | "keywords": [ 14 | "service worker", 15 | "network", 16 | "testing", 17 | "mock", 18 | "fetch" 19 | ], 20 | "author": "Jxck", 21 | "license": "MIT" 22 | } 23 | -------------------------------------------------------------------------------- /sample/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |