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

Reinstate timestamps for mock calls #6672

Open
acostalima opened this issue Jul 10, 2018 · 19 comments
Open

Reinstate timestamps for mock calls #6672

acostalima opened this issue Jul 10, 2018 · 19 comments

Comments

@acostalima
Copy link

🚀 Feature Proposal

Record the timestamp of individual mock calls.

Motivation

I am well aware timestamps were removed in favor of invocationCallOrder(#5867). However, to implement a new matcher proposed in jest-community/jest-extended#112, timestamps are necessary. I am proposing to restore the implementation alongside invocationCallOrder and, at the same time, address the concerns raised previously regarding the precision issues (jest-community/jest-extended#98 (comment), #4402 (comment)). @brigonzalez was already headed towards a possible solution for this resorting to process.hrtime() instead of Date.

Example

One potential scenario for this feature is, by making use of the aforementioned matcher, to ensure a function is called in compliance with a given exponential back-off strategy.

Pitch

I believe this feature supports a legitimate use case and other may follow.

@SimenB
Copy link
Member

SimenB commented Jul 11, 2018

Not sure about this one. What if people want a timestamp for when it returned instead of invoked? Or if it's a promise, when it settled?

Checking the order of invocations makes sense to me (error handler invoked before logger, or whatever). Not sure about how close to mocks were called. We don't want a too big of an API surface. If you really want that, you could in theory implement it as you mockImplementation, and just push into some array you have in your test or something.

@SimenB
Copy link
Member

SimenB commented Jul 11, 2018

@thymikee @rickhanlonii thoughts? Also @UselessPickles since you've been working on the mocks lately 🙂

@UselessPickles
Copy link
Contributor

It would be fairly simple to update the mock code to record 2 timestamps per call:

  • Time of call/invokation
  • Time of completion (return or throw)

There's already a convenient structure containing the "result" data where we could also store the time of completion. But the current structure of the rest of the "mock" object would require that we add yet another parallel array to contain invocation/call timestamps.

It would be nice to combine all of the parallel arrays into a single array of "mock call" objects some day, but that's a big breaking change.

So my thoughts are that it's simple and low risk to implement, so it might be a worthwhile inclusion if it helps others write custom matchers that analyze the relationships between different method calls.

@UselessPickles
Copy link
Contributor

UselessPickles commented Jul 11, 2018

...but the real question is how we obtain the the "now" timestamp. I haven't fully researched this, but I'm vaguely aware of some options:

  • Date.now() is reliably supported, but limited to no better than millisecond precision, and can be thrown off if the system time is manually or automatically adjusted during execution.
  • performance.now() isn't thrown off by system clock changes during execution, and may return better than millisecond precision in some implementations, but can we assume it is supported in all environments where Jest is run?
  • I believe there's also some NodeJS-specific process timestamp that can be obtained that is high precision.

Also note that performance.now() and the NodeJS process timestamp are NOT real date-time timestamps. They start at zero at the beginning of execution and count how long the process has been running. Is that a problem? Will anyone expect these timestamps to be absolute timestamps, or is it OK for them to be relative to the beginning of the process?

@SimenB
Copy link
Member

SimenB commented Jul 11, 2018

I believe there's also some NodeJS-specific process timestamp that can be obtained that is high precision.

process.hrtime is that one :) we can use https://www.npmjs.com/package/browser-process-hrtime which should work in all envs we care about

@acostalima
Copy link
Author

acostalima commented Jul 11, 2018

@UselessPickles @SimenB Yep, that's it. At least for the use case I presented, timestamps relative to the beginning of the process will do. The package convert-hrtime may prove itself useful for this.

@acostalima
Copy link
Author

@UselessPickles @SimenB what's the way forward?

@acostalima
Copy link
Author

acostalima commented Aug 24, 2018

Ping @UselessPickles @SimenB

@UselessPickles
Copy link
Contributor

I think we need this PR merged first, because it includes changes to related code/structures: #6381

Once that is merged, I could easily create a PR to store invocation and completion timestamps, but it might be time to seriously think about restructuring the parallel arrays of information about mock calls. Without any restructuring, it would require adding yet another parallel array to store the invocation timestamps (which may be "good enough" for now to avoid serious breaking changes). I would like feedback from the Jest team on this before I put any work into it.

No matter what approach we take, it will technically be a breaking change to the mock serializer.

The completion timestamp would simply be a new property on the MockFunctionResult structure that would be undefined while the type property is incomplete, then updated to a timestamp value when the function either returns or throws.

@UselessPickles
Copy link
Contributor

UselessPickles commented Nov 1, 2018

@SimenB Now that #6381 is merged, what are your thoughts on this issue?
(see previous comment for some options to consider)

@SimenB
Copy link
Member

SimenB commented Nov 1, 2018

I wonder if we should have jestMock.mockInvocations (or something) be an array of this shape:

type MockInvocation = {
  call: { args: Array<any> },
  result: { type: 'return' | 'throw', value: any },
  // Drop it, maybe? #7070
  instance: any
};

Then we could in theory add some more stuff

@thymikee @rickhanlonii thoughts?

@UselessPickles
Copy link
Contributor

I don't see any reason to nest args in a call property.

Also, a slight correction on the type of result (can be undefined while the function is incomplete), adding timestamps for this issue, and some suggestions for #7070

type MockResult = { 
  type: 'return' | 'throw', 
  value: any,
  // timestamp of function completion
  timestamp: number,
  // #7070 - if we want to get fancy and correctly determine whether 
  // the function is called with `new` and created a new instance
  newInstance: any 
};

type MockInvocation = {
  args: Array<any>,
  result: MockResult | void,
  // #7070 - rename of `instance` to indicate what it really means.
  // This is simply the "this" context upon which the function was called.
  thisContext: any,
  // timestamp of function invocation
  timestamp: number
};

@SimenB
Copy link
Member

SimenB commented Nov 4, 2018

I like it! Do we want a real timestamp, or just something relative to process startup? Depends on if we want higher precision than milliseconds

@rickhanlonii rickhanlonii added this to the Jest 24 milestone Dec 2, 2018
@rickhanlonii
Copy link
Member

Looks like this is stalled (sorry, I've been afk)

Can we wrap it up for Jest 24? What do we need?

@UselessPickles
Copy link
Contributor

@SimenB

Do we want a real timestamp, or just something relative to process startup?

You suggested using browser-process-hrtime previously: #6672 (comment)

And there seemed to be agreement previously that time relative to process startup is sufficient.

@rickhanlonii

Can we wrap it up for Jest 24? What do we need?

Updating the mock code itself to use the structures suggested in my previous comment would be easy. The harder parts are:

I could probably update the mock code and structures itself to help get it started, but I don't think I'll have time to deal with all the other "harder parts" I listed. My free time this winter is consumed by a project car.

@SimenB
Copy link
Member

SimenB commented Dec 13, 2018

there seemed to be agreement previously that time relative to process startup is sufficient.

SGTM.

Adding unit tests for the new timestamps (tricky because it involves timestamps).

Can mock whatever module we use for timing and increment with 1 or something for each invocation

@SimenB SimenB removed this from the Jest 24 milestone Jan 11, 2019
@github-actions
Copy link

This issue is stale because it has been open for 1 year with no activity. Remove stale label or comment or this will be closed in 14 days.

@github-actions github-actions bot added the Stale label Feb 25, 2022
@mrazauskas
Copy link
Contributor

type MockResult = { 
  type: 'return' | 'throw', 
  value: any,
  // timestamp of function completion
  timestamp: number,
  // #7070 - if we want to get fancy and correctly determine whether 
  // the function is called with `new` and created a new instance
  newInstance: any 
};

type MockInvocation = {
  args: Array<any>,
  result: MockResult | void,
  // #7070 - rename of `instance` to indicate what it really means.
  // This is simply the "this" context upon which the function was called.
  thisContext: any,
  // timestamp of function invocation
  timestamp: number
};

Oh.. That is beautiful. Let’s not stale yet.

I have noticed one funny issue with return types of spyOn. Looked around. This could help to solve.

@github-actions github-actions bot removed the Stale label Mar 25, 2022
@github-actions
Copy link

This issue is stale because it has been open for 1 year with no activity. Remove stale label or comment or this will be closed in 30 days.

@github-actions github-actions bot added the Stale label Mar 25, 2023
@SimenB SimenB added Pinned and removed Stale labels Mar 25, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants