Skip to content

Commit

Permalink
Response: Adopts functional composition of "res"
Browse files Browse the repository at this point in the history
  • Loading branch information
kettanaito committed Nov 18, 2018
1 parent 1c34ad5 commit 34e4c69
Show file tree
Hide file tree
Showing 15 changed files with 229 additions and 213 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
__*
.DS_*
node_modules
lib
8 changes: 8 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"arrowParens": "always",
"bracketSpacing": true,
"semi": false,
"useTabs": false,
"trailingComma": "all",
"singleQuote": true
}
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"editor.formatOnSave": true
}
36 changes: 17 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

### Problems of traditional mocking:

* Often relies on a mocking server which you need to run and maintain;
* Doesn't really mock requests, rather **replaces** requests' urls, so they go to the mocking server, instead of the production server;
* Brings extra dependencies to your application, instead of being a dependency-free development tool;
- Often relies on a mocking server which you need to run and maintain;
- Doesn't really mock requests, rather **replaces** requests' urls, so they go to the mocking server, instead of the production server;
- Brings extra dependencies to your application, instead of being a dependency-free development tool;

## Getting started

Expand All @@ -23,22 +23,20 @@ import { MSW } from 'not-published-yet'
const msw = new MSW()

/* Configure mocking routes */
msw.get('https://api.github.com/repo/:repoName', (req, res) => {
/* Access request's params */
const { repoName } = req.params

res
/* Set custom response status */
.status(403)
/* Set headers */
.set({ 'Custom-Header': 'foo' })
/* Delay the response */
.delay(1000)
/* Mock the body */
.json({
errorMessage: `Repository "${repoName}" not found`
})
})
msw.get(
'https://api.github.com/repo/:repoName',
(req, res, { status, set, delay, json }) => {
const { repoName } = req.params // acces request's params

return res(
status(403), // set custom response status
set({ 'Custom-Header': 'foo' }), // set headers
delay(1000), // delay the response
json({
errorMessage: `Repository "${repoName}" not found`,
}),
)
)

/* Start the Service Worker */
msw.start()
Expand Down
57 changes: 29 additions & 28 deletions example/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,35 @@ const { MSW } = MockServiceWorker

const msw = new MSW()

msw.get('https://github.com/user/:username', (req, res) => {
console.log({req})
res
.status(301, 'Custom status text')
.set({
'Header-One': 'first',
'Header-Two': 'second',
})
.json({
...req.params,
message: `This is not a GitHub API, but it may be.`,
param: 'value'
})
})

msw.post('https://github.com/repo/:repoName', (req, res) => {
res
.set('Custom-Header', 'value')
.json({
msw.get(
'https://github.com/user/:username',
(req, res, { status, set, json }) => {
return res(
status(301, 'Custom status text'),
set({
'Header-One': 'foo',
'Header-Two': 'bar',
}),
json({
message: 'This is not a GitHub API',
username: req.params.username,
}),
)
},
)

msw.post('https://github.com/repo/:repoName', (req, res, { set, json }) => {
return res(
set('Custom-Header', 'value'),
json({
repository: req.params.repoName,
message: 'This repo is amazing'
})
message: 'This repo is amazing',
}),
)
})

msw.post('https://api.website.com', (req, res) => {
res
.delay(2000)
.json({ message: 'Delayed response' })
msw.post('https://api.website.com', (req, res, { json, delay }) => {
return res(delay(2000), json({ message: 'Delayed response message' }))
})

msw.start()
Expand All @@ -39,10 +40,10 @@ msw.start()
document.getElementById('btn').addEventListener('click', () => {
fetch('https://github.com/user/kettanaito', {
mode: 'no-cors',
cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached
credentials: "same-origin", // include, *same-origin, omit
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', // include, *same-origin, omit
headers: {
"Content-Type": "application/json; charset=utf-8",
'Content-Type': 'application/json; charset=utf-8',
},
})
})
Expand Down
74 changes: 36 additions & 38 deletions mockServiceWorker.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@ self.addEventListener('install', (event) => {

self.addEventListener('activate', (event) => {
event.waitUntil(self.clients.claim())
console.log('%cMockServiceWorker is activated!', 'color:green;font-weight:bold;')
console.log(
'%cMockServiceWorker is activated!',
'color:green;font-weight:bold;',
)
})

self.addEventListener('message', (event) => {
switch (event.data) {
case 'mock-activate': {
self.__mockActive = true
break;
break
}

case 'mock-deactivate': {
self.__mockActive = false
break;
break
}
}
})
Expand All @@ -40,45 +43,40 @@ const sendMessageToClient = (client, message) => {
self.addEventListener('fetch', async (event) => {
const { clientId, request: req } = event

const defaultResponse = () => {
return fetch(req)
}
const defaultResponse = () => fetch(req)

return event.respondWith(new Promise(async (resolve, reject) => {
const client = await event.target.clients.get(clientId)
if (!client || !self.__mockActive) {
return resolve(defaultResponse())
}
event.respondWith(
new Promise(async (resolve, reject) => {
const client = await event.target.clients.get(clientId)
if (!client || !self.__mockActive) {
return resolve(defaultResponse())
}

const reqHeaders = {}
req.headers.forEach((value, name) => {
reqHeaders[name] = value
})
const reqHeaders = {}
req.headers.forEach((value, name) => {
reqHeaders[name] = value
})

const clientResponse = await sendMessageToClient(client, {
url: req.url,
method: req.method,
headers: reqHeaders,
cache: req.cache,
mode: req.mode,
credentials: req.credentials,
redirect: req.redirect,
referrer: req.referrer,
referrerPolicy: req.referrerPolicy
})
const clientResponse = await sendMessageToClient(client, {
url: req.url,
method: req.method,
headers: reqHeaders,
cache: req.cache,
mode: req.mode,
credentials: req.credentials,
redirect: req.redirect,
referrer: req.referrer,
referrerPolicy: req.referrerPolicy,
})

if (clientResponse === 'not-found') {
return resolve(defaultResponse())
}
if (clientResponse === 'not-found') {
return resolve(defaultResponse())
}

const res = JSON.parse(clientResponse)
const { body, timeout, headers, statusCode, statusText } = res
const mockedResponse = new Response(body, {
headers,
status: statusCode,
statusText,
})
const res = JSON.parse(clientResponse)
const mockedResponse = new Response(res.body, res)

setTimeout(resolve.bind(this, mockedResponse), timeout)
}).catch(console.error))
setTimeout(resolve.bind(this, mockedResponse), res.delay)
}).catch(console.error),
)
})
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"cross-env": "^5.2.0",
"express": "^4.16.4",
"jest": "^23.6.0",
"prettier": "^1.15.2",
"typescript": "^3.1.6",
"webpack": "^4.25.1",
"webpack-cli": "^3.1.2"
Expand Down
76 changes: 76 additions & 0 deletions src/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import * as R from 'ramda'

/**
* Sets the given header, or the Map of headers to the response.
* @example
* res(set('Content-Type', 'plain/text'))
* res(set({ 'Content-Type': 'plain/text', 'My-Header': 'foo' }))
*/
export const set = (name: string | Object, value?: string) => {
return R.ifElse(
R.always(R.is(Object, name)),
R.mergeDeepLeft({ headers: name }),
R.assocPath(['headers', name], value),
)
}

/**
* Sets a status code and optional status text of the response.
* @example
* res(status(301)) // status code only
* res(status(200, 'Fine')) // with status code
*/
export const status = (statusCode: number, statusText?: string) => {
return R.compose(
R.assoc('status', statusCode),
R.when(
R.complement(R.always(R.isNil(statusText))),
R.assoc('statusText', statusText),
),
)
}

/**
* Sets the given text as the body of the response.
* @example
* res(text('Message'))
*/
export const text = (body: string) => {
return R.compose(
R.assoc('body', body),
set('Content-Type', 'text/plain'),
)
}

/**
* Sets the given XML as the body of the response.
* @example
* res(xml('<message>Foo</message>'))
*/
export const xml = (body: string) => {
return R.compose(
R.assoc('body', body),
set('Content-Type', 'text/xml'),
)
}

/**
* Sets the given Object as the JSON body of the response.
* @example
* res(json({ foo: 'bar' }))
*/
export const json = (body: Object) => {
return R.compose(
R.assoc('body', JSON.stringify(body)),
set('Content-Type', 'application/json'),
)
}

/**
* Delays the current response for the given duration (in ms)
* @example
* res(delay(1500), json({ foo: 'bar' }))
*/
export const delay = (duration: number) => {
return R.assoc('delay', duration)
}
Loading

0 comments on commit 34e4c69

Please sign in to comment.