-
-
Notifications
You must be signed in to change notification settings - Fork 2k
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鈥檒l occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adds isAborted method and joiners array property to mock task #2297
Conversation
馃 Changeset detectedLatest commit: 9cc6729 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
packages/testing-utils/src/index.js
Outdated
let _result | ||
let _error | ||
|
||
return { | ||
[TASK]: true, | ||
isRunning: () => _isRunning, | ||
isAborted: () => _isAborted, |
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 always returns a constant value - false
. Shouldn't we expose a similar setter as we do for other "statuses" (setRunning
, setError
, etc)? I also wonder - some of those can't be true at the same time, so I think that it would make sense to create an internal status
variable, initialize it to running'
and then switch it appropriately based on those setters. That way we won't end up with an impossible task with t.isRunning() === true && t.isAborted() === 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.
In general, it's fairly easy to inspect how the real task behaves here:
redux-saga/packages/core/src/internal/newTask.js
Lines 118 to 141 in 4da7bd9
isRunning: () => status === RUNNING, | |
/* | |
This method is used both for answering the cancellation status of the task and answering for CANCELLED effects. | |
In most cases, the cancellation of a task propagates to all its unfinished children (including | |
all forked tasks and the mainTask), so a naive implementation of this method would be: | |
`() => status === CANCELLED || mainTask.status === CANCELLED` | |
But there are cases that the task is aborted by an error and the abortion caused the mainTask to be cancelled. | |
In such cases, the task is supposed to be aborted rather than cancelled, however the above naive implementation | |
would return true for `task.isCancelled()`. So we need make sure that the task is running before accessing | |
mainTask.status. | |
There are cases that the task is cancelled when the mainTask is done (the task is waiting for forked children | |
when cancellation occurs). In such cases, you may wonder `yield io.cancelled()` would return true because | |
`status === CANCELLED` holds, and which is wrong. However, after the mainTask is done, the iterator cannot yield | |
any further effects, so we can ignore such cases. | |
See discussions in #1704 | |
*/ | |
isCancelled: () => status === CANCELLED || (status === RUNNING && mainTask.status === CANCELLED), | |
isAborted: () => status === ABORTED, | |
result: () => taskResult, | |
error: () => taskError, | |
} |
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.
Sure, good point; updated the PR with your suggestion.
@@ -107,7 +107,8 @@ export interface SagaIteratorClone extends SagaIterator { | |||
export function createMockTask(): MockTask | |||
|
|||
export interface MockTask extends Task { | |||
setRunning(running: boolean): void | |||
setRunning(): void |
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 refactor to a status
enum required changing this type signature.
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 could add a @deprecated
signature back and handle both at the implementation level - task.setRunning()
should just be synonymous with task.setRunning(true)
. I'm not 100% sure what task.setRunning(false)
should correspond to though... it seems like canceled would fit better here than aborted
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.
For all of the times I've used this in the past, I've always had to do:
mockTask.setResult(X)
mockTask.setRunning(false)
This is necessary to get the rest of the saga to correctly proceed when joining on a mock task.
I've never had to do setRunning(true)
because the tasks are already running when created.
I think taking out setRunning altogether is simpler than trying to guess what people would expect. Maybe that means publishing another major version? I think since it's a testing utility if we did it in a minor version it would be acceptable.
packages/testing-utils/src/index.js
Outdated
setRunning: b => (_isRunning = b), | ||
setResult: r => (_result = r), | ||
setError: e => (_error = e), | ||
setRunning: () => (status = RUNNING), |
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.
just a random thought without full analysis - perhaps setRunning
should just be deprecated? it seems to me that making a mock task forcefully take a transition that isn't possible in the real code is asking for trouble.
Valid transitions are those:
running
->done
(can be achieved by callingsetResult
)running
->aborted
(can be done by callingsetError
)running
->canceled
(could be done bycancel
)
Thoughts?
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 agree, I think that's a good idea.
Went ahead and added checks for that and tests to cover it :)
@Andarist addressed all your comments and updated the PR description, PTAL. |
@@ -107,7 +107,6 @@ export interface SagaIteratorClone extends SagaIterator { | |||
export function createMockTask(): MockTask | |||
|
|||
export interface MockTask extends Task { | |||
setRunning(running: boolean): void |
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 totally agree with this change - I am concerned about this being a breaking change though. I'm not opposed to releasing a new major version of the Redux Saga or accompanying packages but this seems like a minor cleanup that doesn't necessarily require us to immediately bump a major version.
This PR will probably change the behavior of this task mock slightly, one way or another. But removing a known method is a big breaking change (for this package alone). Could we think about something less disruptive here? My preference would be to add this method back and introduce @deprecated
+ console.warn
for what we want to phase out.
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.
Ok, re-added with a deprecation warning.
let _isRunning = true | ||
let _result | ||
let _error | ||
let status = RUNNING |
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.
nit: in general, it feels that keeping this as a number doesn't buy us anything here since we have to remap this later to string anyway when throwing an error. We could just ditch that if we'd use string status here.
I don't feel strongly about this though - both approaches are fine.
assertStatusRunning(status) | ||
status = CANCELLED | ||
}, | ||
joiners: [], |
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 have actually never used this mock task myself - it was here when I got involved in the project and I never bothered to tweak/remove it. I'm also very rusty when it comes to this codebase as I wasn't actively using it for a couple of last years. I wonder though - how useful this mock is in its current shape.
It seems to me that this now won't throw when passed to a join
but at the same time the join
effect will never get resolved.
I wonder if setError
works OK when it comes to aborting the parent task etc. Do we have any tests for that? Does it 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.
All of the new tests in this PR are pretty comprehensive IMO of describing what behavior works vs what doesn't. If you set the error before it's passed to a join effect it will correctly throw and abort the parent task.
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.
Just a changeset away from landing this :)
When doing saga integration testing (using
expectSaga
from redux-saga-test-plan), creating a mock task (passed into the saga using thefork.fn
static providers) that's passed to a realjoin
effect throws an error. AddingisAborted
and thejoiners
array are necessary for it to not throw an error.During code review, it was determined that
createMockTask
should have an API more similar to the real task implementation.setRunning
was deprecated, and trying to change the state of a task more than once will now throw an error.Also includes new unit tests for covering basic usage, and a small update to the docs with slightly improved instructions.