Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add request assertion feature to server
The server tracks all handled requests and provides an API to allow users to check whether a request matching the given parameters has been made since the server was started. ```javascript server.received.get.to('/contacts', { name: 'Darth Vader' }) server.didNotReceive.post.to('any'); server.received.get.to(/\/secrets.*/); server.didNotReceive.request.to('/robot/5'); ``` There is also a getter for the last request made to the server: ```javascript server.lastRequest ```
- Loading branch information
Aaron Quamme
committed
Sep 12, 2016
1 parent
c07911a
commit 4779275
Showing
3 changed files
with
219 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
/* * RequestAssertion implements a simple DSL for asserting that specific requests were made. | ||
* ``` | ||
* let it = new RequestAssertion(requests); | ||
* | ||
* it.received.delete.to('/posts'); | ||
* it.didNotReceive.request.to('/contacts'); | ||
* it.received.get.to(/\/contacts\/\d+\/); | ||
* it.received.put.to('/posts/5', { title: 'The Title' }); | ||
* it.received.post.with('/listing', { price: 5000 }); | ||
* it.didNotReceive.post.to('any'); | ||
* ``` | ||
* | ||
*/ | ||
import _isPlainObject from 'lodash/lang/isPlainObject'; | ||
|
||
function requestMatches(request, verb, url, params) { | ||
return verbMatches(request, verb) && | ||
urlMatches(request, url) && | ||
paramsMatch(request, params); | ||
|
||
function verbMatches(request, expectedVerb) { | ||
return expectedVerb === 'request' || // 'request' matches all HTTP verbs | ||
request.method.toUpperCase() === expectedVerb.toUpperCase(); | ||
} | ||
|
||
function urlMatches(request, expectedUrl) { | ||
let url = request.url.replace(/\?.*$/, ''); // remove query params | ||
return !expectedUrl || // if no url is passed, then we don't care -- so consider it a match | ||
expectedUrl === 'any' || // 'any' matches all urls | ||
expectedUrl instanceof RegExp && expectedUrl.test(url) || | ||
expectedUrl === url; | ||
} | ||
|
||
function paramsMatch(request, expectedParams) { | ||
// if no params are passed, consider it a match | ||
if (!_isPlainObject(expectedParams)) { | ||
return true; | ||
} | ||
|
||
let actualParams; | ||
try { | ||
actualParams = JSON.parse(request.requestBody || NaN); // || NaN makes parse throw if requestBody is null | ||
} catch (e) { | ||
actualParams = request.queryParams; | ||
} | ||
|
||
return Object.keys(expectedParams).every(k => String(actualParams[k]) === String(expectedParams[k])); | ||
} | ||
} | ||
|
||
function to(requests, expectMatch, verb, ...toParams) { | ||
if (requests.length === 0) { | ||
return expectMatch === false; | ||
} | ||
|
||
let url = (typeof toParams[0] === 'string' || toParams[0] instanceof RegExp) && toParams[0] || null; | ||
let requestParams = _isPlainObject(toParams[0]) && toParams[0] || _isPlainObject(toParams[1]) && toParams[1]; | ||
|
||
let foundMatch = requests.some(request => requestMatches(request, verb, url, requestParams)); | ||
|
||
return foundMatch === expectMatch; | ||
} | ||
|
||
class Verb { | ||
constructor(requests, received) { | ||
['get', 'post', 'put', 'delete', 'head', 'request'].forEach(verb => { | ||
Object.defineProperty(this, verb, { | ||
get() { | ||
let f = to.bind(null, requests, received, verb); | ||
return { to: f, with: f }; | ||
} | ||
}); | ||
}); | ||
} | ||
} | ||
|
||
export default class RequestAssertion { | ||
constructor(requests = []) { | ||
this._requests = requests; | ||
} | ||
|
||
get received() { | ||
return new Verb(this._requests, true); | ||
} | ||
|
||
get didNotReceive() { | ||
return new Verb(this._requests, false); | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import {module, test} from 'qunit'; | ||
import Server from 'ember-cli-mirage/server'; | ||
|
||
module('Integration | Server Request Assertion', { | ||
beforeEach() { | ||
this.server = new Server({ | ||
environment: 'development' | ||
}); | ||
this.server.timing = 0; | ||
this.server.logging = false; | ||
}, | ||
afterEach() { | ||
this.server.shutdown(); | ||
} | ||
}); | ||
|
||
test('server reports no requests', function(assert) { | ||
assert.expect(1); | ||
|
||
assert.ok(this.server.didNotReceive.get.to('any')); | ||
}); | ||
|
||
['get', 'post', 'put', 'delete', 'head'].forEach(verb => { | ||
test(`reports ${verb} to '/contacts'`, function(assert) { | ||
assert.expect(4); | ||
let done = assert.async(); | ||
|
||
this.server[verb]('/contacts', () => true); | ||
|
||
$.ajax({ | ||
method: verb, | ||
url: '/contacts' | ||
}).done(() => { | ||
assert.ok(this.server.received[verb].to('/contacts')); | ||
assert.ok(this.server.received.request.to('/contacts')); | ||
assert.notOk(this.server.didNotReceive[verb].to('/contacts')); | ||
assert.notOk(this.server.didNotReceive.request.to('/contacts')); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
|
||
test('matches query params', function(assert) { | ||
assert.expect(6); | ||
let done = assert.async(); | ||
|
||
this.server.get('/contacts', () => true); | ||
|
||
$.ajax({ | ||
method: 'GET', | ||
url: '/contacts', | ||
data: { page: 4, sort: 'name' } | ||
}).done(() => { | ||
assert.ok(this.server.received.get.to('/contacts', { page: 4 })); | ||
assert.ok(this.server.received.get.to('/contacts', { sort: 'name' })); | ||
assert.ok(this.server.received.get.to('/contacts', { page: 4, sort: 'name' })); | ||
assert.ok(this.server.received.get.with({ page: 4 })); | ||
assert.ok(this.server.received.get.with({ sort: 'name' })); | ||
assert.ok(this.server.received.get.with({ page: 4, sort: 'name' })); | ||
done(); | ||
}); | ||
}); | ||
|
||
test('matches post params', function(assert) { | ||
assert.expect(4); | ||
let done = assert.async(); | ||
|
||
this.server.post('/contacts', () => true); | ||
|
||
$.ajax({ | ||
method: 'POST', | ||
url: '/contacts', | ||
data: JSON.stringify({ name: 'Bilbo', age: 111 }) | ||
}).done(() => { | ||
assert.ok(this.server.received.post.to('/contacts', { name: 'Bilbo', age: 111 })); | ||
assert.ok(this.server.received.post.with({ name: 'Bilbo' })); | ||
assert.notOk(this.server.received.post.to('/contacts', { name: 'Frodo' })); | ||
assert.notOk(this.server.received.post.with({ name: 'Frodo' })); | ||
done(); | ||
}); | ||
}); | ||
|
||
test('to accepts RegExps', function(assert) { | ||
assert.expect(2); | ||
let done = assert.async(); | ||
|
||
this.server.get('/contacts/:id', () => true); | ||
|
||
$.ajax({ | ||
method: 'GET', | ||
url: '/contacts/5' | ||
}).done(() => { | ||
assert.ok(this.server.received.get.to(/\/contacts\/\d+$/)); | ||
assert.notOk(this.server.received.get.to(/\/shizzles\/\d+$/)); | ||
done(); | ||
}); | ||
}); | ||
|
||
test('lastRequest returns the last request that was made', function(assert) { | ||
assert.expect(1); | ||
let done = assert.async(); | ||
|
||
this.server.get('/contacts', () => true); | ||
this.server.get('/posts', () => true); | ||
|
||
$.ajax({ url: '/contacts' }).done(() => { | ||
$.ajax('/posts').done(() => { | ||
assert.equal(this.server.lastRequest.url, '/posts'); | ||
done(); | ||
}); | ||
}); | ||
}); |