-
Notifications
You must be signed in to change notification settings - Fork 829
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
[instrumentation-fetch] refactor fetch() tests for clarity, type safety and realism #5268
Conversation
Refactor `fetch()` tests for improved clearity, type safety and realism (relative to the real-world `fetch` behavior). See the inline comments on PR open-telemetry#5268 for a detailed explaination of the changes.
1b5ad66
to
e188496
Compare
@@ -176,7 +176,7 @@ function testForCorrectEvents( | |||
|
|||
describe('fetch', () => { | |||
let contextManager: ZoneContextManager; | |||
let lastResponse: any | undefined; | |||
let lastResponse: Response | undefined; |
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.
This changes more than the type! Despite the name, this variable previously wasn't storing the Response
object of the last fetch()
request. It stores something that is kind of the parsed response body (~= await response.json()
) but also with some additional bespoke processing/normalization that didn't turn out to be necessary.
This changes things so lastResponse
actually stores the Response
object from the last fetch()
request made in the test. Any test that cares about the response body can call await lastResponse.json()
in the test itself and do what it needs to do there.
@@ -210,12 +210,21 @@ describe('fetch', () => { | |||
sinon.stub(core.otperformance, 'now').callsFake(() => fakeNow); | |||
|
|||
function fakeFetch(input: RequestInfo | Request, init: RequestInit = {}) { | |||
// Once upon a time, there was a bug (#2411), causing a `Request` object | |||
// to be incorrectly passed to `fetch()` as the second argument | |||
assert.ok(!(init instanceof Request)); |
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.
This is a replacement for the init instanceof Request
branch below. We don't have this bug anymore, but this assertion will catch it if it regress, and instantly fail all the tests (synchronously).
const response: any = { | ||
args: {}, | ||
url: fileUrl, | ||
}; |
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.
This variable is really the "default response body", but it also serves a double-duty as the ResponseInit
(second argument to new Response()
, which is very confusing. I separated those into appropriately named separate variables.
To be clear, no tests actually cares that the mock "server response body" has the status in it.
|
||
// This is the default response body expected by the few tests that cares | ||
let responseBody = JSON.stringify({ | ||
isServerResponse: 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.
args: {}
in the diff is only used by a single test, and all it does with it is assert.deepEquals(parsedResponseBody.args, {})
. Maybe it used to do more, but I inferred that these days the only purpose it serves is "did I get the mock response I expected", and so I replaced that with this field.
// Passing request as 2nd argument causes missing body bug (#2411) | ||
response.status = 400; | ||
response.statusText = 'Bad Request (Request object as 2nd argument)'; | ||
reject(new window.Response(JSON.stringify(response), response)); |
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.
This branch was replaced entirely. Note that:
- It is testing for the bug in fix(instrumentation-fetch):
fetch(string, Request)
silently drops request body #2411 which has now be fixed, so this branch is never exercised in practice. - To avoid a regression, I replaced the spirit of this with the
assert.ok
at the top. - This behavior is misleading and in correct – the real
fetch()
neverreject()
the fetch promise with a response. Even when the server returns an HTTP error (response.ok === false
), the promise is still resolved.
In any case, this branch is not needed anymore.
} | ||
|
||
resolve(new window.Response(responseBody, responseInit)); |
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.
We now always resolve, as the real fetch()
would. The 404 branch is also never exercised in the tests (as evident by the fact that I changed it from resolve()
to reject()
and nothing broke). As mentioned above, rejecting with a response is never correct.
If we are rejecting here because it's an internal assertion that indicating some test is set up wrong, I'd rather see this reject with an regular Error, or just throw synchronously at the top.
Object.keys(lastResponse.headers).forEach(key => { | ||
headers[key.toLowerCase()] = lastResponse.headers[key]; | ||
}); | ||
lastResponse.headers = headers; |
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.
This is the convoluted normalization that didn't turn out to be needed (other than removing this part, most of the diff is just indentation change from removing the now-unecessary try/catch.
As far as I can tell, (other than the previously unfortunate variable naming) all this would do is to normalize the request headers into lower case. However, nothing broke when I stopped doing this, so evidently the two tests that cares don't actually need this.
assert.strictEqual( | ||
lastResponse.headers[X_B3_TRACE_ID], | ||
request.headers[X_B3_TRACE_ID], |
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 previously badly named variable made this very confusing, but this is actually asserting that we made the request with the correct headers. Specifically that when the URL is not ignored, the instrumentation code will add these additional headers (which then gets put into the mock server response).
assert.fail(response.statusText); | ||
} | ||
); | ||
}); |
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.
This test was added for #2411. It is basically testing that nothing breaks when passing a Request
object into fetch
works. However, that pattern is now ubiquitously used in numerous other tests (e.g. see around L550), so I'm not sure this is still necessary anymore. Also as mentioned about, this test is not very realistically written, as the real fetch()
would never reject()
with a Response
like this.
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #5268 +/- ##
=======================================
Coverage 94.62% 94.62%
=======================================
Files 323 323
Lines 8068 8068
Branches 1637 1637
=======================================
Hits 7634 7634
Misses 434 434 |
Refactor `fetch()` tests for improved clearity, type safety and realism (relative to the real-world `fetch` behavior). See the inline comments on PR open-telemetry#5268 for a detailed explaination of the changes.
e188496
to
e279803
Compare
rebased and retargeted |
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.
This looks great, thank you for cleaning up the tests here. 🙌
I have to admit that I lack a lot of context on the @opentelemetry/instrumenation-fetch
and @opentelemetry/instrumenation-xhr
packages as they pre-date my time on the project, so the comments you provided were a immensely helpful in figuring out what's going on. Reviewing and having to gather context would've taken me at lot longer if they had not been there.
Ah, looks like there's some conflict somewhere (I can't resolve due to maintainer edits being off). cc @open-telemetry/javascript-approvers if you see the conflicts resolved while I'm ooo please give it another quick look merge this in, thanks 🙂 |
experimental/packages/opentelemetry-instrumentation-fetch/test/fetch.test.ts
Outdated
Show resolved
Hide resolved
Refactor `fetch()` tests for improved clearity, type safety and realism (relative to the real-world `fetch` behavior). See the inline comments on PR open-telemetry#5268 for a detailed explaination of the changes.
7b0494c
to
d6d4424
Compare
Removed the CHANGELOG entry (need a |
Refactor `fetch()` tests for improved clearity, type safety and realism (relative to the real-world `fetch` behavior). See the inline comments on PR open-telemetry#5268 for a detailed explaination of the changes.
d6d4424
to
5ee5d8f
Compare
9c46e8f
Which problem is this PR solving?
Refactor
fetch()
tests for improved clearity, type safety and realism (relative to the real-worldfetch
behavior).I would like to propose something for #4888 to start a discussion, but in doing so I find that the current tests are quite confusing to work with, so I split out these refactors to the tests as a separate PR/proposal.
I think this represents a significant enough improvements to the tests that it makes sense as a PR, but
I may something else that I want to try/propose next weekin the longer term, I am also proposing that we further refactor these tests in #5282.Short description of the changes
See the inline commits on the PR.
Type of change
Please delete options that are not relevant.
How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
npm run lint
npm run test:browser
Checklist:
addedupdated