Skip to content

Commit 37450d0

Browse files
committed
Add regex matching on path.
1 parent e4c2006 commit 37450d0

File tree

9 files changed

+147
-31
lines changed

9 files changed

+147
-31
lines changed

Gruntfile.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ module.exports = function(grunt) {
7373
grunt.registerTask('host-example', ['connect:example:keepalive']);
7474
grunt.registerTask('example', ['connect:example', 'protractor:example']);
7575
grunt.registerTask('test', ['jasmine_node']);
76+
grunt.registerTask('lint', ['jshint']);
7677
grunt.registerTask('client-test', ['browserify:test', 'jasmine:test']);
7778

7879
grunt.registerTask('verify', ['test', 'client-test', 'example']);
79-
};
80+
};

README.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Protractor Mock
2-
A NodeJS module to be used alongside [Protractor](https://github.com/angular/protractor) to facilitate setting up mocks for HTTP calls for the AngularJS applications under test.
2+
A NodeJS module to be used alongside [Protractor](https://github.com/angular/protractor) to facilitate setting up mocks for HTTP calls for the AngularJS applications under test.
33

44
This allows the developer to isolate the UI and client-side application code in our tests without any dependencies on an API.
55

@@ -69,11 +69,11 @@ Make sure to clean up after test execution. This should be typically done in the
6969
afterEach(function(){
7070
mock.teardown();
7171
});
72-
72+
7373
Please note that the `mock()` function needs to be called before the browser opens. If you have different mock data for different tests, please make sure that, either the tests always start in a new browser window, or that its possible to setup all the mocks for each test case before any of tests start running.
7474

7575
### Mock files
76-
Mocks can also be loaded from physical files located in the `mocks.dir` directory that we defined in our configuration:
76+
Mocks can also be loaded from physical files located in the `mocks.dir` directory that we defined in our configuration:
7777

7878
tests
7979
e2e
@@ -103,6 +103,7 @@ The full GET schema for defining your mocks is as follows:
103103
request: {
104104
path: '/products/1/items',
105105
method: 'GET',
106+
regex: false, // Boolean to enable Regular Expression matching on path. This is an optional field.
106107
params: { // These match params as they would be passed to the $http service. This is an optional field.
107108
page: 2,
108109
status: 'onsale'
@@ -127,6 +128,7 @@ A full mock for a POST call takes the following options:
127128
request: {
128129
path: '/products/1/items',
129130
method: 'POST',
131+
regex: false, // Boolean to enable Regular Expression matching on path. This is an optional field.
130132
data: { // These match POST data. This is an optional field.
131133
status: 'onsale',
132134
title: 'Blue Jeans',
@@ -151,7 +153,7 @@ Defining `params`, `queryString`, `headers`, or `data` will help the plugin matc
151153
Headers must be defined as the headers that will be used in the http call. Therefore, if in the code to be tested, the headers are defined using properties with function values, these functions will be evaluated as per the $http specification and matched by end result.
152154

153155
#### Response
154-
The default `status` value is 200 if none is specified.
156+
The default `status` value is 200 if none is specified.
155157

156158
An optional `delay` value can be set on the response to assert any state that occurs when waiting for the response in your application, i.e. loading messages or spinners. Please note that UI tests with timing expectations can be somewhat unstable and provide inconsistent results. Please use this feature carefully.
157159

@@ -212,7 +214,7 @@ These will dynamically modify your current set of mocks, and any new request tha
212214

213215
Plugins can be used to extend the matching functionality of protractor-http-mock. These are separate from protractor plugins.
214216

215-
A plugin can be defined as either an NPM package or a function.
217+
A plugin can be defined as either an NPM package or a function.
216218

217219
They can be declared in your protractor configuration to be consumed by all your tests:
218220

@@ -225,7 +227,7 @@ They can be declared in your protractor configuration to be consumed by all your
225227
}
226228

227229
or in each individual test:
228-
230+
229231
mock([
230232
//mocks go here
231233
], [
@@ -243,7 +245,7 @@ See this [sample plugin](https://github.com/atecarlos/protractor-http-mock-sampl
243245
### Defaults
244246

245247
If necessary, default mocks and plugins can be skipped for a particular test simply by passing true at the end of your `mock` call:
246-
248+
247249
mock(mocks, plugins, true);
248250

249251

example/app/app.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,22 @@ angular
1616
return $http({ method: 'POST', url: '/users/new', data: data });
1717
},
1818
getBy: function(name, city){
19-
return $http({
20-
method: 'GET',
19+
return $http({
20+
method: 'GET',
2121
url: '/users',
2222
params: {
2323
name: name,
2424
city: city
2525
}
2626
});
2727
},
28+
getById: function(id){
29+
console.log(id);
30+
return $http({
31+
method: 'GET',
32+
url: '/users/' + id
33+
});
34+
},
2835
getByQuery: function(name, city){
2936
return $http({
3037
method: 'GET',
@@ -168,6 +175,12 @@ angular
168175
.catch(catchHandler);
169176
};
170177

178+
self.searchById = function() {
179+
userService.getById(self.query)
180+
.then(searchHandler)
181+
.catch(catchHandler);
182+
};
183+
171184
self.searchByQuery = function(){
172185
userService.getByQuery(self.query, self.queryCity)
173186
.then(searchHandler)

example/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
<button id="user-search-button" ng-click="ctrl.search()">Search</button>
3838
<button id="user-search-by-query-button" ng-click="ctrl.searchByQuery()">Search</button>
3939
<button id="user-search-external-by-query-button" ng-click="ctrl.searchExternal()">Search</button>
40+
<button id="user-search-by-id-button" ng-click="ctrl.searchById()">Search by ID</button>
4041
</div>
4142

4243
<div class="form">

example/spec/regex.spec.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
var mock = require('../../index'),
2+
get = require('./get');
3+
4+
describe('match path with regex', function(){
5+
beforeEach(function(){
6+
mock([
7+
{
8+
request: {
9+
path: '\\/users\\/[0-9]',
10+
method: 'GET',
11+
regex: true
12+
},
13+
response: {
14+
status: 200,
15+
data: {
16+
name: 'User with int id'
17+
}
18+
}
19+
},
20+
{
21+
request: {
22+
path: '\\/users\\/[^0-9]',
23+
method: 'GET',
24+
regex: true
25+
},
26+
response: {
27+
status: 400,
28+
data: {
29+
error: 'Not a number'
30+
}
31+
}
32+
}
33+
]);
34+
});
35+
36+
afterEach(function(){
37+
mock.teardown();
38+
});
39+
40+
it('match on int id', function(){
41+
get();
42+
43+
var searchButton = element(by.id('user-search-by-id-button'));
44+
45+
element(by.id('user-query')).sendKeys('1');
46+
searchButton.click();
47+
48+
expect(element(by.id('user-data')).getText()).toContain('User with int id');
49+
50+
element(by.id('user-query')).clear().sendKeys('abc');
51+
searchButton.click();
52+
53+
expect(element(by.id('errorMsg')).getText()).toBe('Not a number');
54+
});
55+
});

lib/httpMock.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ function mockTemplate() {
9292
return response;
9393
}
9494

95+
function matchRegex(pattern, string){
96+
var regex = new RegExp(pattern);
97+
return regex.test(string);
98+
}
99+
95100
function endsWith(url, path){
96101
var questionMarkIndex = url.indexOf('?');
97102

@@ -156,7 +161,7 @@ function mockTemplate() {
156161

157162
function matchByPlugins(expectationRequest, config){
158163
var match = true;
159-
164+
160165
if(plugins.length > 0){
161166
match = plugins.reduce(function(value, plugin){
162167
return plugin(expectationRequest, config) && value;
@@ -168,7 +173,8 @@ function mockTemplate() {
168173

169174
function match(config, expectationRequest){
170175
return matchMethod(expectationRequest, config) &&
171-
endsWith(config.url, expectationRequest.path) &&
176+
(expectationRequest.regex ? matchRegex(expectationRequest.path, config.url)
177+
: endsWith(config.url, expectationRequest.path)) &&
172178
matchParams(expectationRequest, config) &&
173179
matchData(expectationRequest, config) &&
174180
matchQueryString(expectationRequest, config) &&
@@ -219,7 +225,7 @@ function mockTemplate() {
219225
}
220226

221227
return responseHeaders[headerName];
222-
}
228+
};
223229
}
224230

225231
function httpMock(config){
@@ -388,7 +394,7 @@ module.exports = function(expectations, plugins){
388394
var template = templateString.substring(templateString.indexOf('{') + 1, templateString.lastIndexOf('}'));
389395
var pluginsString = getPluginsString(plugins);
390396

391-
var newFunc =
397+
var newFunc =
392398
template
393399
.replace(/'<place_content_here>'/, '[' + getExpectationsString(expectations) + ']')
394400
.replace(/'<place_query_string_parse_here>'/, queryString.parse.toString())

lib/initData.js

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* globals browser */
12
'use strict';
23

34
var httpMock = require('./httpMock'),
@@ -6,7 +7,7 @@ var httpMock = require('./httpMock'),
67

78
function getConfig(){
89
var config = defaultConfig;
9-
10+
1011
if(module.exports.config){
1112
config.rootDirectory = module.exports.config['rootDirectory'] || config.module.rootDirectory;
1213
}
@@ -29,31 +30,27 @@ function readMockFile(mockDirectory, mock){
2930
return require(path.join(mockDirectory, mock));
3031
}
3132

32-
function getDefaultKeys(mocksConfig){
33-
return mocksConfig.default && mocksConfig.default.length > 0 ? mocksConfig.default : [];
34-
}
35-
3633
function buildMocks(mocks, skipDefaults){
3734
var data = [],
3835
config = getConfig(),
3936
mockDirectory = path.join(config.rootDirectory, config.mocks.dir);
4037

4138
mocks = mocks || [];
42-
39+
4340
if(!skipDefaults){
4441
mocks = config.mocks.default.concat(mocks);
4542
}
4643

4744
for(var i = 0; i < mocks.length; i++){
4845
// TODO: add validation check
4946
var dataModule = typeof mocks[i] === 'string' ? readMockFile(mockDirectory, mocks[i]) : mocks[i];
50-
47+
5148
if(Array.isArray(dataModule)){
5249
data = data.concat(dataModule);
5350
}else{
5451
data.push(dataModule);
5552
}
56-
53+
5754
}
5855

5956
return data;
@@ -97,32 +94,32 @@ module.exports.teardown = function(){
9794

9895
module.exports.requestsMade = function() {
9996
return browser.executeAsyncScript(function () {
100-
var httpMock = angular.module("httpMock");
101-
var callback = arguments[arguments.length - 1]
97+
var httpMock = angular.module('httpMock');
98+
var callback = arguments[arguments.length - 1];
10299
callback(httpMock.requests);
103100
});
104101
};
105102

106103
module.exports.clearRequests = function(){
107104
return browser.executeAsyncScript(function () {
108-
angular.module("httpMock").clearRequests();
109-
var callback = arguments[arguments.length - 1]
105+
angular.module('httpMock').clearRequests();
106+
var callback = arguments[arguments.length - 1];
110107
callback(true);
111108
});
112109
};
113110

114111
module.exports.add = function(mocks){
115112
return browser.executeAsyncScript(function () {
116-
angular.module("httpMock").addMocks(arguments[0]);
117-
var callback = arguments[arguments.length - 1]
113+
angular.module('httpMock').addMocks(arguments[0]);
114+
var callback = arguments[arguments.length - 1];
118115
callback(true);
119116
}, mocks);
120117
};
121118

122119
module.exports.remove = function(mocks){
123120
return browser.executeAsyncScript(function () {
124-
angular.module("httpMock").removeMocks(arguments[0]);
125-
var callback = arguments[arguments.length - 1]
121+
angular.module('httpMock').removeMocks(arguments[0]);
122+
var callback = arguments[arguments.length - 1];
126123
callback(JSON.stringify(true));
127124
}, mocks);
128-
};
125+
};

tests/regex.test.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
describe('regex match', function(){
2+
var http;
3+
4+
beforeAll(function(){
5+
http = window.__getHttp();
6+
});
7+
8+
it('matches any', function(done){
9+
http.get('/regex/d3ce5994-e662-4223-9968-9fc01694f08f').then(function(response){
10+
expect(response.data).toBe('regex any match');
11+
done();
12+
});
13+
});
14+
15+
it('matches number', function(done){
16+
http.get('/regex/1').then(function(response){
17+
expect(response.data).toBe('regex number match');
18+
done();
19+
});
20+
});
21+
});

tests/setup.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,26 @@
254254
response: {
255255
data: 'plugin match works!'
256256
}
257+
},
258+
{
259+
request: {
260+
path: 'regex\\/.*',
261+
regex: true,
262+
method: 'get',
263+
},
264+
response: {
265+
data: 'regex any match'
266+
}
267+
},
268+
{
269+
request: {
270+
path: '\\/regex\\/[0-9]',
271+
regex: true,
272+
method: 'get',
273+
},
274+
response: {
275+
data: 'regex number match'
276+
}
257277
}
258278
];
259279

0 commit comments

Comments
 (0)