Skip to content

Commit

Permalink
Add new config option to use jsonPath for response selector.
Browse files Browse the repository at this point in the history
New 'jsonPathSwitchResponse' option allows to skip creating separate 'get' responses for each request parameter value in favor of selecting those respones from a list that is prepared for listing objects from backend (e.g. '/users' -> 'users/:id').
  • Loading branch information
pawel-mika committed May 22, 2017
1 parent 98efced commit ac4e165
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 7 deletions.
3 changes: 2 additions & 1 deletion .gitignore
@@ -1,3 +1,4 @@
node_modules/*
mocks/*
.DS_Store
.DS_Store
.vscode/*
74 changes: 72 additions & 2 deletions README.md
Expand Up @@ -213,7 +213,7 @@ will return data from the mock file called "king.json", with HTTP status 200.
Any other password will return "sorry.json" with HTTP status 401.

#### JsonPath Support
For complex JSON requests, JsonPath expressions are supported in the switch parameter. If your switch parameter begins with "$." then it will be evaluated as a JsonPath expression.
For complex JSON requests, JsonPath expressions are supported in the switch parameter. If your switch parameter begins with "$." then it will be evaluated as a JsonPath expression.
For example to switch the response based on the value of the last occurence of ItemId in a JSON request, use configuration as shown for "aceinsleeve":
```js
"switch": "$..ItemId[(@.length-1)]",
Expand All @@ -227,6 +227,76 @@ For example to switch the response based on the value of the last occurence of I
According to this configuration, if the value of the last occurence of ItemId is 4, the mockFile "ItemId4.aceinsleeve.json" will be retured with a HTTP status code of 500. Otherwise, mockFile "aceinsleeve.json"
will be returned with HTTP status 200. Note: If the JsonPath expression evaluates to more then 1 element (for example, all books cheaper than 10 as in $.store.book[?(@.price < 10)] ) then the first element is considered for testing the value.

#### JsonPath with Switch Response support
For requests that without any params should be returning a list of items (e.g. `/users`), and with some param just single item (e.g. `/users/:id`) there are special configuration options provided to select those single items from prepared mock json file containing list of items. No need to create separate files per each parameter.
Example mock file could look like this:
```js
[
{
"name": "Han Solo",
"role": "pilot",
"id": 1
},
{
"name": "Chewbacca",
"role": "first officer",
"id": 2
},
{
"name": "C3P0",
"role": "droid",
"id": 3
},
{
"name": "R2D2",
"role": "droid",
"id": 4
}
]
```

and example configurataion like this:

```js
"users": {
"mockFile": "users.json",
"verbs": [
"get"
]
},
"users/:id": {
"mockFile": "users.json",
"verbs": [
"get"
],
"switch": "id",
"jsonPathSwitchResponse": {
"jsonpath": "$[?(@.id==#id#)]",
"mockFile": "users.json",
"forceFirstObject": true
}
},
"users/role/:role": {
"mockFile": "users.json",
"verbs": [
"get"
],
"switch": "role",
"jsonPathSwitchResponse": {
"jsonpath": "$[?(@.role==\"#role#\")]",
"mockFile": "users.json",
"forceFirstObject": false
}
}
```

The first config property (`users`) contains just a standard `get` for all users. The second (`users/:id`) and third though (`users/role/:role`), contains a proper switch configuration and `jsonPathSwitchResponse` config that contains following parameters:
* jsonpath - this is a JsonPath selector for objects to match inside `mockFile`; parameters values from switch are transferred to it's corresponding names wrapped in `#` characters,
* mockFile - a file name with mocked response to search through,
* forceFirstOject - (default: false) this is a switch telling if we should return all found items as an array, or select first one and return it as an object.

So it is possible to select just a single user by id as an object (`/users/1`), but it is also possible to return multiple users as an array (`users/role/droid`).

#### RegExp Support
As an alternative to JsonPath, Javascript Regular Expressions are supported in the switch parameter. See unit tests in the test.js file for examples of using Regular Expressions.

Expand Down Expand Up @@ -262,7 +332,7 @@ templateSample.json
{
"Name": "@Name",
"Number": "@Number"
}
}
```

When you call /John/12345 you will be returned:
Expand Down
36 changes: 33 additions & 3 deletions config.json
Expand Up @@ -56,11 +56,11 @@
"userIduser1passwordgood": {"httpStatus": 200, "mockFile": "king.json"},
"userIdadminpasswordgood": {"httpStatus": 200}
}
},
},
"template/:name/:number" :{
"mockFile": "templateSample.json",
"enableTemplate": true,
"verbs":["get"],
"enableTemplate": true,
"verbs":["get"],
"contentType":"application/json"
},
"templateSwitchGetParams" : {
Expand Down Expand Up @@ -88,6 +88,36 @@
"switch": "$.data.user.userAge",
"type": "jsonpath"}],
"contentType": "application/json"
},
"users": {
"mockFile": "users.json",
"verbs": [
"get"
]
},
"users/:id": {
"mockFile": "users.json",
"verbs": [
"get"
],
"switch": "id",
"jsonPathSwitchResponse": {
"jsonpath": "$[?(@.id==#id#)]",
"mockFile": "users.json",
"forceFirstObject": true
}
},
"users/role/:role": {
"mockFile": "users.json",
"verbs": [
"get"
],
"switch": "role",
"jsonPathSwitchResponse": {
"jsonpath": "$[?(@.role==\"#role#\")]",
"mockFile": "users.json",
"forceFirstObject": false
}
}
}
}
23 changes: 22 additions & 1 deletion lib/apimocker.js
Expand Up @@ -239,7 +239,8 @@ apiMocker.sendResponse = function(req, res, serviceKeys) {
res.send(options.httpStatus);
return;
}
if (options.switch) {

if (options.switch && !options.jsonPathSwitchResponse) {
options = _.clone(options);
originalOptions = _.clone(options);
apiMocker.setSwitchOptions(options, req);
Expand Down Expand Up @@ -270,6 +271,26 @@ apiMocker.sendResponse = function(req, res, serviceKeys) {
return;
}

if (options.switch && options.jsonPathSwitchResponse) {
var jpath = options.jsonPathSwitchResponse.jsonpath;
var fpath = path.join(apiMocker.options.mockDirectory, options.jsonPathSwitchResponse.mockFile);
var forceFirstObject = options.jsonPathSwitchResponse.forceFirstObject || false;
_.each(_.keys(req.params), function (key) {
var param = '#key#'.replace('key', key);
jpath = jpath.replace(param, req.params[key]);
});
try {
var fd = fs.openSync(fpath, 'r');
var mockFile = fs.readFileSync(fpath, { encoding: 'utf8' });
var allElems = jsonPath.eval(JSON.parse(mockFile), jpath);
res.status(options.httpStatus || 200).send(forceFirstObject ? allElems[0] : allElems);
} catch (err) {
apiMocker.log(err);
res.send(options.httpStatus || 404);
}
return;
}

mockPath = path.join(apiMocker.options.mockDirectory, options.mockFile);
apiMocker.log('Returning mock: ' + options.verb.toUpperCase() + ' ' + options.serviceUrl + ' : ' +
options.mockFile);
Expand Down
22 changes: 22 additions & 0 deletions samplemocks/users.json
@@ -0,0 +1,22 @@
[
{
"name": "Han Solo",
"role": "pilot",
"id": 1
},
{
"name": "Chewbacca",
"role": "first officer",
"id": 2
},
{
"name": "C3P0",
"role": "droid",
"id": 3
},
{
"name": "R2D2",
"role": "droid",
"id": 4
}
]
30 changes: 30 additions & 0 deletions test/test-config.json
Expand Up @@ -108,6 +108,36 @@
"switch": "$.data.user.userAge",
"type": "jsonpath"}],
"contentType": "application/json"
},
"users": {
"mockFile": "users.json",
"verbs": [
"get"
]
},
"users/:id": {
"mockFile": "users.json",
"verbs": [
"get"
],
"switch": "id",
"jsonPathSwitchResponse": {
"jsonpath": "$[?(@.id==#id#)]",
"mockFile": "users.json",
"forceFirstObject": true
}
},
"users/role/:role": {
"mockFile": "users.json",
"verbs": [
"get"
],
"switch": "role",
"jsonPathSwitchResponse": {
"jsonpath": "$[?(@.role==\"#role#\")]",
"mockFile": "users.json",
"forceFirstObject": false
}
}
}
}
20 changes: 20 additions & 0 deletions test/test-functional.js
Expand Up @@ -228,6 +228,26 @@ describe('Functional tests using an http client to test "end-to-end": ', functio
});
});

describe('jsonPatch switch response', function () {
it('returns proper single object from mockFile', function (done) {
var reqOptions = httpReqOptions('/users/1');
verifyResponseBody(reqOptions, null, { name: 'Han Solo', role: 'pilot', id: 1 }, done);
});
it('returns proper array of results', function (done) {
var reqOptions = httpReqOptions('/users/role/droid');
var expected = [{
name: 'C3P0',
role: 'droid',
id: 3
}, {
name: 'R2D2',
role: 'droid',
id: 4
}];
verifyResponseBody(reqOptions, null, expected, done);
});
});

describe('http status: ', function() {
it('returns 404 for incorrect path', function(done) {
stRequest.get('/badurl')
Expand Down

0 comments on commit ac4e165

Please sign in to comment.