Skip to content

Commit

Permalink
Merge fd658ca into bc565eb
Browse files Browse the repository at this point in the history
  • Loading branch information
pimterry committed Oct 12, 2016
2 parents bc565eb + fd658ca commit f8eddd5
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 79 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ 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)
Expand Down Expand Up @@ -810,6 +811,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 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
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
38 changes: 13 additions & 25 deletions lib/scope.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,38 +119,26 @@ 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) {
var persistedAndUsed = self._persist && interceptor.interceptionCounter > 0;
return !persistedAndUsed && !interceptor.optional;
});
return pendingInterceptors.length > 0;
});

return pendingInterceptorKeys;
};

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
56 changes: 20 additions & 36 deletions tests/browserify-public/browserify-bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -1219,6 +1219,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 @@ -1311,17 +1318,6 @@ Interceptor.prototype.replyWithFile = function replyWithFile(statusCode, filePat
return this.reply(statusCode, readStream, headers);
};

Interceptor.prototype.replyWithFile = function replyWithFile(statusCode, filePath, headers) {
if (! fs) {
throw new Error('No fs');
}
var readStream = fs.createReadStream(filePath);
readStream.pause();
this.filePath = filePath;
return this.reply(statusCode, readStream, headers);
};


// Also match request headers
// https://github.com/pgte/nock/issues/163
Interceptor.prototype.reqheaderMatches = function reqheaderMatches(options, key) {
Expand Down Expand Up @@ -2828,38 +2824,26 @@ 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) {
var persistedAndUsed = self._persist && interceptor.interceptionCounter > 0;
return !persistedAndUsed && !interceptor.optional;
});
return pendingInterceptors.length > 0;
});

return pendingInterceptorKeys;
};

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
73 changes: 55 additions & 18 deletions tests/test_intercept.js
Original file line number Diff line number Diff line change
Expand Up @@ -622,22 +622,6 @@ test("isDone", function(t) {
req.end();
});

test("requireDone", function(t) {
var scope = nock('http://www.google.com')
.get('/', false, { requireDone: false })
.reply(200, "Hello World!");

t.ok(scope.isDone(), "done when a requireDone is set to false");

scope.get('/', false, { requireDone: true})
.reply(200, "Hello World!");

t.notOk(scope.isDone(), "not done when a requireDone is explicitly set to true");

nock.cleanAll()
t.end();
});

test("request headers exposed", function(t) {

var scope = nock('http://www.headdy.com')
Expand Down Expand Up @@ -2297,6 +2281,39 @@ test('pending mocks works', function(t) {
});
});

test('pending mocks doesn\'t include optional mocks', function(t) {
var scope = nock('http://example.com')
.get('/nonexistent')
.optionally()
.reply(200);

t.deepEqual(nock.pendingMocks(), []);
t.end();
});

test('optional mocks are still functional', function(t) {
var scope = nock('http://example.com')
.get('/abc')
.optionally()
.reply(200);

var req = http.get({host: 'example.com', path: '/abc'}, function(res) {
t.assert(res.statusCode === 200, "should still mock requests");
t.deepEqual(nock.pendingMocks(), []);
t.end();
});
});

test('isDone is true with optional mocks outstanding', function(t) {
var scope = nock('http://example.com')
.get('/abc')
.optionally()
.reply(200);

t.ok(scope.isDone());
t.end();
});

test('username and password works', function(t) {
var scope = nock('http://passwordyy.com')
.get('/')
Expand Down Expand Up @@ -2444,6 +2461,28 @@ test('persists interceptors', function(t) {
}).end();
});

test('Persisted interceptors are in pendingMocks initially', function(t) {
var scope = nock('http://example.com')
.get('/abc')
.reply(200, "Persisted reply")
.persist();

t.deepEqual(scope.pendingMocks(), ["GET http://example.com:80/abc"]);
t.end();
});

test('Persisted interceptors are not in pendingMocks after the first request', function(t) {
var scope = nock('http://example.com')
.get('/def')
.reply(200, "Persisted reply")
.persist();

http.get('http://example.com/def', function(res) {
t.deepEqual(scope.pendingMocks(), []);
t.end();
});
});

test("persist reply with file", function(t) {
var scope = nock('http://www.filereplier.com')
.persist()
Expand Down Expand Up @@ -4401,7 +4440,6 @@ test('remove interceptor for not found resource', function(t) {
});

test('isDone() must consider repeated responses', function(t) {

var scope = nock('http://www.example.com')
.get('/')
.times(2)
Expand Down Expand Up @@ -4431,7 +4469,6 @@ test('isDone() must consider repeated responses', function(t) {
t.end();
});
});

});

test('you must setup an interceptor for each request', function(t) {
Expand Down

0 comments on commit f8eddd5

Please sign in to comment.