Skip to content

Commit

Permalink
implementation of redirected property (#197)
Browse files Browse the repository at this point in the history
* implementation of redirected property

* improved inline docs
  • Loading branch information
wheresrhys authored Jun 24, 2017
1 parent 67081f5 commit 3b4d151
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 5 deletions.
1 change: 1 addition & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Replaces `fetch()` with a stub which records its calls, grouped by route, and op
* `headers`: key/value map of headers to match
* `matcher`: as specified above
* `response`: as specified above
* `__redirectUrl`: *experimental* the url the response should be from (to imitate followed redirects - will set `redirected: true` on the response)
* `times`: An integer, `n`, limiting the number of times the matcher can be used. If the route has already been called `n` times the route will be ignored and the call to `fetch()` will fall through to be handled by any other routes defined (which may eventually result in an error if nothing matches it)

#### `once()`
Expand Down
49 changes: 44 additions & 5 deletions src/fetch-mock.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,43 +144,70 @@ FetchMock.prototype.mockResponse = function (url, responseConfig, fetchOpts, res
responseConfig = responseConfig(url, fetchOpts);
}

// If the response is a pre-made Response, respond with it
if (FetchMock.Response.prototype.isPrototypeOf(responseConfig)) {
return this.respond(Promise.resolve(responseConfig), resolveHoldingPromise);
}

// If the response says to throw an error, throw it
if (responseConfig.throws) {
return this.respond(Promise.reject(responseConfig.throws), resolveHoldingPromise);
}

// If the response config looks like a status, start to generate a simple response
if (typeof responseConfig === 'number') {
responseConfig = {
status: responseConfig
};
} else if (typeof responseConfig === 'string' || !(responseConfig.body || responseConfig.headers || responseConfig.throws || responseConfig.status)) {
// If the response config is not an object, or is an object that doesn't use
// any reserved properties, assume it is meant to be the body of the response
} else if (typeof responseConfig === 'string' || !(
responseConfig.body ||
responseConfig.headers ||
responseConfig.throws ||
responseConfig.status ||
responseConfig.__redirectUrl
)) {
responseConfig = {
body: responseConfig
};
}

// Now we are sure we're dealing with a response config object, so start to
// construct a real response from it
const opts = responseConfig.opts || {};
opts.url = url;
opts.sendAsJson = responseConfig.sendAsJson === undefined ? FetchMock.config.sendAsJson : responseConfig.sendAsJson;

// set the response url
opts.url = responseConfig.__redirectUrl || url;

// Handle a reasonably common misuse of the library - returning an object
// with the property 'status'
if (responseConfig.status && (typeof responseConfig.status !== 'number' || parseInt(responseConfig.status, 10) !== responseConfig.status || responseConfig.status < 200 || responseConfig.status > 599)) {
throw new TypeError(`Invalid status ${responseConfig.status} passed on response object.
To respond with a JSON object that has status as a property assign the object to body
e.g. {"body": {"status: "registered"}}`);
}

// set up the response status
opts.status = responseConfig.status || 200;
opts.statusText = FetchMock.statusTextMap['' + opts.status];
// The ternary operator is to cope with new Headers(undefined) throwing in Chrome

// Set up response headers. The ternary operator is to cope with
// new Headers(undefined) throwing in Chrome
// https://code.google.com/p/chromium/issues/detail?id=335871
opts.headers = responseConfig.headers ? new FetchMock.Headers(responseConfig.headers) : new FetchMock.Headers();

// start to construct the body
let body = responseConfig.body;

// convert to json if we need to
opts.sendAsJson = responseConfig.sendAsJson === undefined ? FetchMock.config.sendAsJson : responseConfig.sendAsJson;
if (opts.sendAsJson && responseConfig.body != null && typeof body === 'object') { //eslint-disable-line
body = JSON.stringify(body);
}

// On the server we need to manually construct the readable stream for the
// Response object (on the client this is done automatically)
if (FetchMock.stream) {
let s = new FetchMock.stream.Readable();
if (body != null) { //eslint-disable-line
Expand All @@ -189,8 +216,19 @@ e.g. {"body": {"status: "registered"}}`);
s.push(null);
body = s;
}
let response = new FetchMock.Response(body, opts);

// When mocking a followed redirect we must wrap the response in an object
// which sets the redirected flag (not a writable property on the actual response)
if (responseConfig.__redirectUrl) {
response = Object.create(response, {
redirected: {
value: true
}
})
}

return this.respond(Promise.resolve(new FetchMock.Response(body, opts)), resolveHoldingPromise);
return this.respond(Promise.resolve(response), resolveHoldingPromise);
}

FetchMock.prototype.respond = function (response, resolveHoldingPromise) {
Expand Down Expand Up @@ -351,4 +389,5 @@ FetchMock.prototype.sandbox = function (Promise) {
}
})


module.exports = FetchMock;
16 changes: 16 additions & 0 deletions test/spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,22 @@ module.exports = (fetchMock, theGlobal, Request, Response) => {
});
});

it('respond with a redirected response', () => {
fetchMock.mock('http://it.at.there/', {
__redirectUrl: 'http://it.at.there/destination',
body: 'I am a redirect'
});
return fetch('http://it.at.there/')
.then(res => {
expect(res.redirected).to.equal(true);
expect(res.url).to.equal('http://it.at.there/destination');
return res.text()
.then(text => {
expect(text).to.equal('I am a redirect')
})
});
});

it('construct a response based on the request', () => {
fetchMock.mock({
name: 'route',
Expand Down

0 comments on commit 3b4d151

Please sign in to comment.