Skip to content

Commit

Permalink
Merge pull request #723 from pimterry/optional-mocks
Browse files Browse the repository at this point in the history
Add optionally() and make isDone and pendingMocks consistent.
  • Loading branch information
vrinek committed Oct 20, 2016
2 parents 5af77f0 + 142c818 commit d5962f2
Show file tree
Hide file tree
Showing 6 changed files with 282 additions and 88 deletions.
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,14 @@ For instance, if a module performs HTTP requests to a CouchDB server or makes HT
- [Path filtering](#path-filtering)
- [Request Body filtering](#request-body-filtering)
- [Request Headers Matching](#request-headers-matching)
- [Optional Requests](#optional-requests)
- [Allow __unmocked__ requests on a mocked hostname](#allow-unmocked-requests-on-a-mocked-hostname)
- [Expectations](#expectations)
- [.isDone()](#isdone)
- [.cleanAll()](#cleanall)
- [.persist()](#persist)
- [.pendingMocks()](#pendingmocks)
- [.activeMocks()](#activemocks)
- [Logging](#logging)
- [Restoring](#restoring)
- [Turning Nock Off (experimental!)](#turning-nock-off-experimental)
Expand Down Expand Up @@ -810,6 +812,23 @@ var scope = nock('http://api.myservice.com')
})
```

## Optional Requests

By default every mocked request is expected to be made exactly once, and until it is it'll appear in `scope.pendingMocks()`, and `scope.isDone()` will return false (see [expectations](#expectations)). In many cases this is fine, but in some (especially cross-test setup code) it's useful to be able to mock a request that may or may not happen. You can do this with `optionally()`. Optional requests are consumed just like normal ones once matched, but they do not appear in `pendingMocks()`, and `isDone()` will return true for scopes with only optional requests pending.

```js
var example = nock("http://example.com");
example.pendingMocks() // []
example.get("/pathA").reply(200);
example.pendingMocks() // ["GET http://example.com:80/path"]

// ...After a request to example.com/pathA:
example.pendingMocks() // []

example.get("/pathB").optionally().reply(200);
example.pendingMocks() // []
```

## Allow __unmocked__ requests on a mocked hostname

If you need some request on the same host name to be mocked and some others to **really** go through the HTTP stack, you can use the `allowUnmocked` option like this:
Expand Down Expand Up @@ -900,6 +919,22 @@ It is also available in the global scope:
console.error('pending mocks: %j', nock.pendingMocks());
```

## .activeMocks()

You can see every mock that is currently active (i.e. might potentially reply to requests) in a scope using `scope.activeMocks()`. A mock is active if it is pending, optional but not yet completed, or persisted. Mocks that have intercepted their requests and are no longer doing anything are the only mocks which won't appear here.

You probably don't need to use this - it mainly exists as a mechanism to recreate the previous (now-changed) behavior of `pendingMocks()`.

```js
console.error('active mocks: %j', scope.activeMocks());
```

It is also available in the global scope:

```js
console.error('active mocks: %j', nock.activeMocks());
```

# Logging

Nock can log matches if you pass in a log function like this:
Expand Down
31 changes: 21 additions & 10 deletions lib/intercept.js
Original file line number Diff line number Diff line change
Expand Up @@ -295,24 +295,34 @@ function isActive() {

}

function isDone() {
return _.every(allInterceptors, function(interceptors) {
return _.every(interceptors.scopes, function(interceptor) {
return interceptor.__nock_scope.isDone();
});
});
}

function pendingMocks() {
function interceptorScopes() {
return _.reduce(allInterceptors, function(result, interceptors) {
for (var interceptor in interceptors.scopes) {
result = result.concat(interceptors.scopes[interceptor].__nock_scope.pendingMocks());
result = result.concat(interceptors.scopes[interceptor].__nock_scope);
}

return result;
}, []);
}

function isDone() {
return _.every(interceptorScopes(), function(scope) {
return scope.isDone();
});
}

function pendingMocks() {
return _.flatten(_.map(interceptorScopes(), function(scope) {
return scope.pendingMocks();
}));
}

function activeMocks() {
return _.flatten(_.map(interceptorScopes(), function(scope) {
return scope.activeMocks();
}));
}

function activate() {

if(originalClientRequest) {
Expand Down Expand Up @@ -392,6 +402,7 @@ module.exports.activate = activate;
module.exports.isActive = isActive;
module.exports.isDone = isDone;
module.exports.pendingMocks = pendingMocks;
module.exports.activeMocks = activeMocks;
module.exports.enableNetConnect = enableNetConnect;
module.exports.disableNetConnect = disableNetConnect;
module.exports.overrideClientRequest = overrideClientRequest;
Expand Down
7 changes: 7 additions & 0 deletions lib/interceptor.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ function Interceptor(scope, uri, method, requestBody, interceptorOptions) {

this.delayInMs = 0;
this.delayConnectionInMs = 0;

this.optional = false;
}

Interceptor.prototype.optionally = function optionally() {
this.optional = true;
return this;
}

Interceptor.prototype.replyWithError = function replyWithError(errorMessage) {
Expand Down
48 changes: 23 additions & 25 deletions lib/scope.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,38 +123,35 @@ Scope.prototype.options = function _options(uri, requestBody, options) {
};

Scope.prototype.pendingMocks = function pendingMocks() {
return Object.keys(this.keyedInterceptors);
var self = this;

var pendingInterceptorKeys = Object.keys(this.keyedInterceptors).filter(function (key) {
var interceptorList = self.keyedInterceptors[key];
var pendingInterceptors = interceptorList.filter(function (interceptor) {
// TODO: This assumes that completed mocks are removed from the keyedInterceptors list
// (when persistence is off). We should change that (and this) in future.
var persistedAndUsed = self._persist && interceptor.interceptionCounter > 0;
return !persistedAndUsed && !interceptor.optional;
});
return pendingInterceptors.length > 0;
});

return pendingInterceptorKeys;
};

// Returns all keyedInterceptors that are active.
// This incomplete interceptors, persisted but complete interceptors, and
// optional interceptors, but not non-persisted and completed interceptors.
Scope.prototype.activeMocks = function activeMocks() {
return Object.keys(this.keyedInterceptors);
}

Scope.prototype.isDone = function isDone() {
var self = this;
// if nock is turned off, it always says it's done
if (! globalIntercept.isOn()) { return true; }

var keys = Object.keys(this.keyedInterceptors);
if (keys.length === 0) {
return true;
} else {
var doneHostCount = 0;

keys.forEach(function(key) {
var doneInterceptorCount = 0;

self.keyedInterceptors[key].forEach(function(interceptor) {
var isRequireDoneDefined = !_.isUndefined(interceptor.options.requireDone);
if (isRequireDoneDefined && interceptor.options.requireDone === false) {
doneInterceptorCount += 1;
} else if (self._persist && interceptor.interceptionCounter > 0) {
doneInterceptorCount += 1;
}
});

if (doneInterceptorCount === self.keyedInterceptors[key].length ) {
doneHostCount += 1;
}
});
return (doneHostCount === keys.length);
}
return this.pendingMocks().length === 0;
};

Scope.prototype.done = function done() {
Expand Down Expand Up @@ -350,6 +347,7 @@ module.exports = extend(startScope, {
isActive: globalIntercept.isActive,
isDone: globalIntercept.isDone,
pendingMocks: globalIntercept.pendingMocks,
activeMocks: globalIntercept.activeMocks,
removeInterceptor: globalIntercept.removeInterceptor,
disableNetConnect: globalIntercept.disableNetConnect,
enableNetConnect: globalIntercept.enableNetConnect,
Expand Down
86 changes: 51 additions & 35 deletions tests/browserify-public/browserify-bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -1074,24 +1074,34 @@ function isActive() {

}

function isDone() {
return _.every(allInterceptors, function(interceptors) {
return _.every(interceptors.scopes, function(interceptor) {
return interceptor.__nock_scope.isDone();
});
});
}

function pendingMocks() {
function interceptorScopes() {
return _.reduce(allInterceptors, function(result, interceptors) {
for (var interceptor in interceptors.scopes) {
result = result.concat(interceptors.scopes[interceptor].__nock_scope.pendingMocks());
result = result.concat(interceptors.scopes[interceptor].__nock_scope);
}

return result;
}, []);
}

function isDone() {
return _.every(interceptorScopes(), function(scope) {
return scope.isDone();
});
}

function pendingMocks() {
return _.flatten(_.map(interceptorScopes(), function(scope) {
return scope.pendingMocks();
}));
}

function activeMocks() {
return _.flatten(_.map(interceptorScopes(), function(scope) {
return scope.activeMocks();
}));
}

function activate() {

if(originalClientRequest) {
Expand Down Expand Up @@ -1171,6 +1181,7 @@ module.exports.activate = activate;
module.exports.isActive = isActive;
module.exports.isDone = isDone;
module.exports.pendingMocks = pendingMocks;
module.exports.activeMocks = activeMocks;
module.exports.enableNetConnect = enableNetConnect;
module.exports.disableNetConnect = disableNetConnect;
module.exports.overrideClientRequest = overrideClientRequest;
Expand Down Expand Up @@ -1221,6 +1232,13 @@ function Interceptor(scope, uri, method, requestBody, interceptorOptions) {

this.delayInMs = 0;
this.delayConnectionInMs = 0;

this.optional = false;
}

Interceptor.prototype.optionally = function optionally() {
this.optional = true;
return this;
}

Interceptor.prototype.replyWithError = function replyWithError(errorMessage) {
Expand Down Expand Up @@ -2819,38 +2837,35 @@ Scope.prototype.delete = function _delete(uri, requestBody, options) {
};

Scope.prototype.pendingMocks = function pendingMocks() {
return Object.keys(this.keyedInterceptors);
var self = this;

var pendingInterceptorKeys = Object.keys(this.keyedInterceptors).filter(function (key) {
var interceptorList = self.keyedInterceptors[key];
var pendingInterceptors = interceptorList.filter(function (interceptor) {
// TODO: This assumes that completed mocks are removed from the keyedInterceptors list
// (when persistence is off). We should change that (and this) in future.
var persistedAndUsed = self._persist && interceptor.interceptionCounter > 0;
return !persistedAndUsed && !interceptor.optional;
});
return pendingInterceptors.length > 0;
});

return pendingInterceptorKeys;
};

// Returns all keyedInterceptors that are active.
// This incomplete interceptors, persisted but complete interceptors, and
// optional interceptors, but not non-persisted and completed interceptors.
Scope.prototype.activeMocks = function activeMocks() {
return Object.keys(this.keyedInterceptors);
}

Scope.prototype.isDone = function isDone() {
var self = this;
// if nock is turned off, it always says it's done
if (! globalIntercept.isOn()) { return true; }

var keys = Object.keys(this.keyedInterceptors);
if (keys.length === 0) {
return true;
} else {
var doneHostCount = 0;

keys.forEach(function(key) {
var doneInterceptorCount = 0;

self.keyedInterceptors[key].forEach(function(interceptor) {
var isRequireDoneDefined = !_.isUndefined(interceptor.options.requireDone);
if (isRequireDoneDefined && interceptor.options.requireDone === false) {
doneInterceptorCount += 1;
} else if (self._persist && interceptor.interceptionCounter > 0) {
doneInterceptorCount += 1;
}
});

if (doneInterceptorCount === self.keyedInterceptors[key].length ) {
doneHostCount += 1;
}
});
return (doneHostCount === keys.length);
}
return this.pendingMocks().length === 0;
};

Scope.prototype.done = function done() {
Expand Down Expand Up @@ -3046,6 +3061,7 @@ module.exports = extend(startScope, {
isActive: globalIntercept.isActive,
isDone: globalIntercept.isDone,
pendingMocks: globalIntercept.pendingMocks,
activeMocks: globalIntercept.activeMocks,
removeInterceptor: globalIntercept.removeInterceptor,
disableNetConnect: globalIntercept.disableNetConnect,
enableNetConnect: globalIntercept.enableNetConnect,
Expand Down
Loading

0 comments on commit d5962f2

Please sign in to comment.