Skip to content
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: remember the request history (nock#2171) #2750

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

sezanzeb
Copy link

@sezanzeb sezanzeb commented Jun 6, 2024

Fixes #2171 properly

also contains updated tests and documentation.

@PersonTheCat
Copy link

PersonTheCat commented Jun 27, 2024

I'm interested in doing some analysis on HTTP requests made by a service of mine. In your opinion--or in the opinion of any maintainer--how suitable would it be for this project to capture all requests (not just intercepted requests)?

Edit: specifically, I'm looking to make assertions about my request bodies for real requests. This is not a hard requirement of mine, just considering my options.

@mikicho
Copy link
Contributor

mikicho commented Jul 14, 2024

@PersonTheCat You probably want the Recording mode
@sezanzeb I like this idea, but I'm currently focusing on releasing the beta. I hope to be able to review this soon and think about its API.

@sezanzeb
Copy link
Author

sezanzeb commented Aug 29, 2024

I'm really looking forward to this. This would make it so much more pleasant for me to work with nock.

  • Jest allows to access the list of calls via .mock.calls
  • Sinon can do .getCalls(), and even has firstCall, secondCall, thirdCall and lastCall

having this similar in nock would feel natural and intuitive.

})

it('remembers requests', async () => {
const interceptor1 = nock('http://example.test').persist().get(/\/dsad1.*/)
Copy link
Contributor

@mikicho mikicho Aug 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm concerned that this may lead to OOM errors and/or memory leak-like behavior.

Copy link
Author

@sezanzeb sezanzeb Aug 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say unit-tests typically don't send enormous payloads, or make thousands of requests.

Anyhow: remembering 2000 get-requests getting "foo" as answer: https://gist.github.com/sezanzeb/d5b89e10bdebf2cfb6192983743aa9eb shows 101MiB of memory usage in my system monitor. Without any requests 48MiB are needed.

Also, requests should be cleaned up after every test, if the interceptor isn't created in beforeEach callbacks. I just realized this still needs to be implemented, like https://jestjs.io/docs/mock-function-api#mockfnmockclear

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with you, but users may use Nock in unexpected ways. WDYT about saving only the last ten requests?

Copy link
Author

@sezanzeb sezanzeb Sep 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is a warning being written to the console when the maximum number of requests is stored, and if it is configurable, it would be an acceptable compromise I think.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good.

Copy link
Author

@sezanzeb sezanzeb Sep 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternative:

nock('http://example.test')
  .persist()
  .get(/\/dsad2.*/)
  .remember(10)  // the argument is null by default: remembers all requests

without .remember(), no requests are saved.

Copy link
Author

@sezanzeb sezanzeb Sep 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is nice and fits well into nock, and is optional, so existing tests won't remember requests. Therefore the limit can be infinite by default. What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding features is easy. Maintaining them is something else :)
I know that using persist is a known pattern with nock, but can you please share with me why you use it instead of regular interceptors?
Don't get me wrong—I'm in favor of inserting this feature to nock, but I think we need to be careful how we implement it.

Copy link
Author

@sezanzeb sezanzeb Sep 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also do

  it('remembers a request', async () => {
    const interceptor = nock('http://example.test')
        .get('/foo')
        .remember()

    interceptor.reply(202, '1')
    await sendRequest('example.test', '/foo')
    expect(interceptor.requests).to.have.lengthOf(1);

    interceptor.reply(202, '1')
    await sendRequest('example.test', '/foo')
    expect(interceptor.requests).to.have.lengthOf(2);
  })

without persist.

I have commited that new test

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the request object should be remembered in the scope as well:

    const scope = nock('http://example.test')
        .get('/foo')
        .remember()
        .reply(202, '1')

    await sendRequest('example.test', '/foo')

    console.log(scope.request)

Though I'm somewhat confused with scopes and interceptors. A scope has a list of this.interceptors. But doing a single .reply(202, '1') on an interceptor creates a scope for a single request, which suggests that a scope belongs to a single interceptor.

@mikicho
Copy link
Contributor

mikicho commented Aug 29, 2024

In general, I like this idea because it let us to move the assertions to the end of the test file, which is more natural:

it('should..', () => {
  // Arrange
  const scope = nock(..).post(..).reply(200);
  
  // Act
  await fetch(...);
  
  // Assert
  expect(scope.requests[0].body).toBe('Hello World');
});

vs today:

it('should..', () => {
  let body;
  // Arrange
  const scope = nock(..).post(..).reply(200, (uri, requestBody) => { body =  requestBody;});
  
  // Act
  await fetch(...);
  
  // Assert
  expect(body).toBe('Hello World');
});

@mikicho
Copy link
Contributor

mikicho commented Sep 1, 2024

I think we need to work on the beta channel instead of the main (I hope to merge it soon). We can backport it if needed.

@@ -305,6 +305,8 @@ class InterceptedRequestRouter {
)

if (matchedInterceptor) {
matchedInterceptor.requests.push(req)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ClientRequest does not have a friendly API (think about getting the body). Maybe we should transform it to a Request instance?

Copy link
Author

@sezanzeb sezanzeb Sep 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know how?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add a way to retrieve the request object
3 participants