-
-
Notifications
You must be signed in to change notification settings - Fork 736
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: remove socketDelay #1974
feat: remove socketDelay #1974
Conversation
From a user's perspective, having both `delayConnection` and `socketDelay` is confusing and unnecessarily. The differences between the two are subtle, and in my experience, users need to be fairly well versed in both the Node HTTP module and the internals of Nock in order to accurately determine which to use. On the surface there seems to be two use cases: - A user wants to test how their code handles a `timeout` event. Whether their client is expected to abort the request, or some other action is taken in response to the event. Either way, the user doesn't care if wall-clock time passes, and would prefer the timeout be simulated so their test suite can get on with it. - A user wants to force wall-clock time to pass before a `response` event. This is usually to test some timeout feature that is not based on the `timeout` event, or to give other code time to complete a task first. Based on those two use cases, it seems obvious that there should be two different delay functions, like we seem to have now. However, there are two subtle aspects that blur the line. - When a socket emits a `timeout` event, which the request propagates, the request does not end. This is true in Node and in Nock today. Clients my choose to abort a request upon a timeout event, they may not. - In Nock today, when the socket is "applying" the artificial timeout, to determine if it should emit a `timeout`, it doesn't just use the value passed to `socketDelay`. It uses the sum of the values passed to `delayConnection` and `socketDelay`. This covers the flow of a user wanting to ensure their code sees a `timeout` event even if no action is taken. Therefore, there is no reason to have two different options for users. The value passed to `delayConnection` can trigger a `timeout` event right away and if the code chooses not to act on the event, then it will wait in real time until `response` is triggered. In fact, when I began working on this, I went into the `test_socket_delay.js` file and replaced all `socketDelay` with `delayConnection`. All the tests passed. Other minor tweaks included in this change: - `removeAll` calls `removeAllTimers` Even without the removal of `socketDelay`, consumers using `delay` and a timeout on the socket would see the request aborted by most clients right way, however, the ref to the timer would hang until the callback fired. - The delay methods on the Interceptor are now just setters instead of additive. This was undocumented, unintuitive behavior. - Fixed a bug from nock#1973, where `replayWithError` would cause Interceptors to be consumed twice. BREAKING CHANGES - `socketDelay` has been removed. Use `delayConnection` instead. - `delay`, `delayConnection`, and `delayBody` are now setters instead of additive. example: ```js nock('http://example.com') .get('/') .delay(1) .delay({ head: 2, body: 3 }) .delayConnection(4) .delayBody(5) .delayBody(6) .reply() ``` Previously, the connection would have been delayed by 7 and the body delayed by 14. Now, the connection will be delayed by 4 and the body delayed by 6.
Your analysis makes sense to me. However the caveat is that I've never used this feature of Nock. It's possible we may get some comments after the fact, either from people who find the new implementation less convenient, or who have a use case which really requires a distinction. I'm fine with making a change like this, with an openness to revisiting it later if necessary. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removeAll
callsremoveAllTimers
Even without the removal ofsocketDelay
, consumers usingdelay
and a timeout on the socket would see the request aborted by most
clients right way, however, the ref to the timer would hang until
the callback fired.
Could you explain this more? Specifically, why is it being called from removeAll()
?
|
||
```js | ||
nock('http://my.server.com') | ||
.get('/') | ||
.socketDelay(2000) // 2 seconds | ||
.delayBody(2000) // 2 seconds |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be good to add a note in the readme that says .socketDelay()
was removed in Nock 13.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome change.
### Delay the response | ||
|
||
Nock can simulate response latency to allow you to test timeouts, race conditions, an other timing related scenarios. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure this is completely accurate, but I'd suggest something like this:
Nock can simulate the effect of response latency to allow you to test timeouts, race conditions, an other timing related scenarios. Rather than waiting for real clock time to pass it compares the requested delay to the timeout parameter passed to the overridden request methods (i.e.
http.request()
andhttp.ClientRequest#setTimeout()
) and emits the timeouts almost immediately. This allows you to test timeouts without using fake timers or slowing down your tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The added detail here is only true when delaying the connection and a timeout is set on the request. So while I'm good with the added verbiage, I think it should go below where the details of delayConnection
are covered.
I was trying to keep this opening section on delays high-level and only include the info the top 90% of users are going to want.
README.md
Outdated
``` | ||
|
||
NOTE: the timeout will be fired immediately, and will not leave the simulated connection idle for the specified period of time. | ||
The timer starts after the [`'response'`](http://nodejs.org/api/http.html#http_event_response) event and will delay the [IncomingMessage](http://nodejs.org/api/http.html#http_http_incomingmessage) from emitting its first `'data'` or `'end'` event. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this one cause a real delay?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I think it is confusing for the caller that one of these methods causes a real delay and the other one doesn't.
Maybe that was true before? Either way, since the semantics of these APIs are changing, I think it is probably a good time to look at it.
One possibility would be to change the names, e.g.:
simulateDelayedConnection(ms)
delayBody(ms)
The docs on
I've always interpreted that as this is the method to call between individual tests to ensure you don't get side effects across tests. Currently, if a test ends while requests are mid-flight and the test suite calls This falls into why the test "should be safe to call in the middle of a request" was added. It turns out, in our test suite, we had coverage being reported for lines that were only ever touched after tests ended. Line 119 in c5bf54c
Tests like "socket has address() method" are completely unrelated, but because they called Adding |
👍 As for "... a use case which really requires a distinction." It seems like a worthwhile trade off to me to have it simpler for 99% of consumers and make the rest, who by definition are already in the nitty-gritty, write 3 lines themselves. |
I trust you on this one. If you want to try to get some feedback from users of the methods that are being removed or changed in behavior, we could release a fix version which logs a message asking for feedback, pointing to this issue. |
To me What's being added feels a bit more like what I think it's reasonable to have a "clean everything" method (as in the lifecycle RFC) although that seems like a separate matter.
The rest of this makes sense. |
Comment and doc updates.
@paulmelnikow I agree with you on the I expanded the comment on the new test to hopefully clarify the why/what/how. And I took another pass on the updates to the README. Also, this is the last breaking change I had lined up when I asked for a beta a few weeks ago. |
@paulmelnikow have you been able to look at this PR again? |
Yea, lemme give it another read. This has been a really busy time but I think I can read it sometime in the next two days. Thanks for the reminder! |
👍
No worries! I've been feeling it too. Sorry for my delay too. My open-source time has been going to Shields these past couple weeks. We just did a big migration to Heroku. The migration was easy but I'm onboarding a new ops team too, and negotiating things with the outgoing sysadmin. As usual the people work is harder than the tech work. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One question left about the readme.
### Delay the connection | ||
While all of Nock's delay functions will delay real clock time for the request/response lifetime, | ||
`timeout` events are emitted almost immediately by comparing the requested connection delay to the timeout parameter passed to `http.request()` or `http.ClientRequest#setTimeout()`. | ||
This allows you to test timeouts without using fake timers or slowing down your tests. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These two things feel like contradictory:
- "While all of Nock's delay functions will delay real clock time for the request/response lifetime"
- "This allows you to test timeouts without using fake timers or slowing down your tests."
How is it that they're both true?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is it that they're both true?
Because a timeout
event does not directly affect the request or response.
Maybe "lifetime" is the wrong wording.
The timeout event is emitted "immediately", if the client chooses to not take an action (eg abort the request) the request and response will continue on as normal, after real time as passed.
Which is identical behavior to how Nock behaves now when a caller uses delayConnection
and sets a timeout on the request (without socketDelay
).
As for not slowing down a users tests, this assumes their code or client does take action on the timeout
event and subsequently the test can finish quick.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahhh I see. I think putting the explanation of real clock time at the end would make it clearer. How about something like this:
Nock emits
timeout
events almost immediately by comparing [...] This allows you to test timeouts without using fake timers or slowing down your tests. If the client chooses to not take an action (e.g. abort the request), the request and response will continue on as normal, after real clock time has passed.
@@ -16,6 +16,7 @@ chai.use(sinonChai) | |||
|
|||
afterEach(function () { | |||
nock.restore() | |||
nock.abortPendingRequests() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One suggested edit. Otherwise good to go!
Co-authored-by: Paul Melnikow <github@paulmelnikow.com>
🎉 This PR is included in version 13.0.0-beta.4 🎉 The release is available on: Your semantic-release bot 📦🚀 |
🎉 This PR is included in version 13.0.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
🎉 This PR is included in version 13.0.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
From a user's perspective, having both
delayConnection
andsocketDelay
is confusing and unnecessarily.The differences between the two are subtle, and in my experience, users
need to be fairly well versed in both the Node HTTP module and the
internals of Nock in order to accurately determine which to use.
On the surface there seems to be two use cases:
timeout
event.Whether their client is expected to abort the request, or some other
action is taken in response to the event. Either way, the user
doesn't care if wall-clock time passes, and would prefer the timeout
be simulated so their test suite can get on with it.
response
event.This is usually to test some timeout feature that is not based on the
timeout
event, or to give other code time to complete a task first.Based on those two use cases, it seems obvious that there should be two
different delay functions, like we seem to have now. However, there are
two subtle aspects that blur the line.
timeout
event, which the request propagates,the request does not end. This is true in Node and in Nock today.
Clients my choose to abort a request upon a timeout event, they may not.
to determine if it should emit a
timeout
, it doesn't just use thevalue passed to
socketDelay
. It uses the sum of the values passedto
delayConnection
andsocketDelay
. This covers the flow of auser wanting to ensure their code sees a
timeout
event even if noaction is taken.
Therefore, there is no reason to have two different options for users.
The value passed to
delayConnection
can trigger atimeout
eventright away and if the code chooses not to act on the event, then it
will wait in real time until
response
is triggered.In fact, when I began working on this, I went into the
test_socket_delay.js
file and replaced allsocketDelay
withdelayConnection
. All the tests passed.Other minor tweaks included in this change:
removeAll
callsremoveAllTimers
Even without the removal of
socketDelay
, consumers usingdelay
and a timeout on the socket would see the request aborted by most
clients right way, however, the ref to the timer would hang until
the callback fired.
of additive. This was undocumented, unintuitive behavior.
replayWithError
would causeInterceptors to be consumed twice.
BREAKING CHANGES
socketDelay
has been removed. UsedelayConnection
instead.delay
,delayConnection
, anddelayBody
are now setters instead of additive.example:
Previously, the connection would have been delayed by 7 and the body delayed by 14.
Now, the connection will be delayed by 4 and the body delayed by 6.