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

Support for mocking nexus operations #1666

Merged
merged 9 commits into from
Oct 28, 2024
Merged

Conversation

rodrigozhou
Copy link
Contributor

@rodrigozhou rodrigozhou commented Oct 10, 2024

What was changed

Support for mocking nexus operations in the test framework.
The user can mock using either the nexus.Operation itself, or using nexus.OperationReference through the nexus.NewOperationReference helper. See the docs in the OnNexusOperation for description and example.

Why?

Be able to mock nexus operation without needing to have access to the operation implementation itself or creating dummy operations.

Checklist

  1. Closes

  2. How was this tested:

  1. Any docs updates needed?

@rodrigozhou rodrigozhou requested a review from a team as a code owner October 10, 2024 05:24
@bergundy
Copy link
Member

This PR could use some tests and there would be an example in the documentation showing how this may be used. I want to understand the experience you had in mind better before diving deeper into the review.

panic(fmt.Sprintf("mock of ExecuteNexusOperation failed to deserialize input"))
}

// rebuild the input as *nexus.LazyValue
Copy link
Member

Choose a reason for hiding this comment

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

Why?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because, after I call input.Consume on L3208, it closed the reader, so it can't call again, which is needed if the user didn't mock the operation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, actually, I can see that the reader isn't really being used at this moment, so maybe I could just skip this...

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, you can avoid this for now since we control both implementations. Might want to just put a comment here but either way it's not critical.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'd rather leave as it to avoid forgetting about this later if things change.

@rodrigozhou rodrigozhou requested review from bergundy and cretz October 11, 2024 04:27
Copy link
Member

@cretz cretz left a comment

Choose a reason for hiding this comment

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

LGTM, but would wait until @Quinn-With-Two-Ns takes a look

Comment on lines 579 to 588
// t.OnNexusOperation(
// service,
// HelloOperation,
// HelloInput{Message: "Temporal"},
// mock.Anything,
// ).Return(
// &nexus.HandlerStartOperationResultAsync{
// OperationID: "hello-operation-id",
// },
// nil,
// )
//
// t.RegisterNexusAsyncOperationCompletion(
// service,
// HelloOperation.Name(),
// "hello-operation-id",
// HelloOutput{Message: "Hello Temporal"},
// nil,
// 1*time.Second,
// )
Copy link
Contributor Author

Choose a reason for hiding this comment

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

In addition to this, maybe we could have helper functions to mock sync and async operations. Example: for async operation, we could have something like this:

func (e *TestWorkflowEnvironment) MockNexusAsyncOperation(
	service any,
	operation any,
	input any,
	options any,
	result any,
	err error,
	delay time.Duration,
) *MockCallWrapper {
	operationID := uuid.New() // maybe also input?
	m := e.OnNexusOperation(service, operation, input, options).Return(
		&nexus.HandlerStartOperationResultAsync{
			OperationID: operationID,
		},
		nil,
	)
	e.RegisterNexusAsyncOperationCompletion(service, operation, operationID, result, err, delay)
	return m
}

And we could also have something similar for the sync which basically abstract the returned value type nexus.HandlerStartOperationResultSync.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I think this would be a more friendly interface. But I'm okay merging what we have for flexibility and opening an issue to track adding a variant as you suggested.

Copy link
Member

Choose a reason for hiding this comment

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

Alternatively, if a user sets After on the returned MockCallWrapper, it sort of makes the operation async already (with the exception of generating a separate started event).
Can you config After worker with the MockCallWrapper returned here?

Copy link
Contributor Author

@rodrigozhou rodrigozhou Oct 15, 2024

Choose a reason for hiding this comment

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

Yes, you can, but as you said, it would not represent the actual behavior of async operation.
I added some unit tests to check the After is doing its job.

Copy link
Member

@bergundy bergundy left a comment

Choose a reason for hiding this comment

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

Overall LGTM. There's just one conversation to resolve and this would be good to merge AFAIC.

go.mod Outdated Show resolved Hide resolved
Comment on lines 579 to 588
// t.OnNexusOperation(
// service,
// HelloOperation,
// HelloInput{Message: "Temporal"},
// mock.Anything,
// ).Return(
// &nexus.HandlerStartOperationResultAsync{
// OperationID: "hello-operation-id",
// },
// nil,
// )
//
// t.RegisterNexusAsyncOperationCompletion(
// service,
// HelloOperation.Name(),
// "hello-operation-id",
// HelloOutput{Message: "Hello Temporal"},
// nil,
// 1*time.Second,
// )
Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I think this would be a more friendly interface. But I'm okay merging what we have for flexibility and opening an issue to track adding a variant as you suggested.

Comment on lines 579 to 588
// t.OnNexusOperation(
// service,
// HelloOperation,
// HelloInput{Message: "Temporal"},
// mock.Anything,
// ).Return(
// &nexus.HandlerStartOperationResultAsync{
// OperationID: "hello-operation-id",
// },
// nil,
// )
//
// t.RegisterNexusAsyncOperationCompletion(
// service,
// HelloOperation.Name(),
// "hello-operation-id",
// HelloOutput{Message: "Hello Temporal"},
// nil,
// 1*time.Second,
// )
Copy link
Member

Choose a reason for hiding this comment

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

Alternatively, if a user sets After on the returned MockCallWrapper, it sort of makes the operation async already (with the exception of generating a separate started event).
Can you config After worker with the MockCallWrapper returned here?

Copy link
Contributor

@Quinn-With-Two-Ns Quinn-With-Two-Ns left a comment

Choose a reason for hiding this comment

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

I don't think this PR handles expectation failure , If I am wrong then could we add a test to verify.

@rodrigozhou
Copy link
Contributor Author

@Quinn-With-Two-Ns I added AssertNexusOperation* functions.

Comment on lines -1002 to -1005
if !(e.workflowMock.AssertCalled(dummyT, methodName, arguments...) || e.activityMock.AssertCalled(dummyT, methodName, arguments...)) {
return e.workflowMock.AssertCalled(t, methodName, arguments...) && e.activityMock.AssertCalled(t, methodName, arguments...)
}
return true
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this code is wrong, as well as AssertNotCalled and AssertNumberOfCalls.

Copy link
Contributor

Choose a reason for hiding this comment

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

What do you mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

L1003 checks if both workflowMock and activityMock was called. I think it was supposed to be if either of them was called. The other functions have similar mistakes.

return e
}

func (e *TestWorkflowEnvironment) SetOnNexusOperationCanceledListener(
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: do we have a test for SetOnNexusOperationCanceledListener?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, I'm not sure how to test it since the SDK itself doesn't support canceling a Nexus operation.

Copy link
Contributor

Choose a reason for hiding this comment

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

You can cancel a nexus operation from the SDK, the SDK can send a cancelation command from a workflow task.

@@ -1184,6 +1186,229 @@ func TestWorkflowTestSuite_NexusSyncOperation_ClientMethods_Panic(t *testing.T)
require.Equal(t, "not implemented in the test environment", panicReason)
}

func TestWorkflowTestSuite_MockNexusOperation(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I don't think we test mocking cancel?

Copy link
Contributor

@Quinn-With-Two-Ns Quinn-With-Two-Ns left a comment

Choose a reason for hiding this comment

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

LGTM! few pieces of test coverage missing but not blocking address if you can. Please point at a released version of the nexus Go SDK before you merge

}
return true
return e.AssertWorkflowCalled(dummyT, methodName, arguments...) ||
e.AssertActivityCalled(dummyT, methodName, arguments...) ||
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are some of these called with dummyT and some t?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's a great question, and I have no idea. I just copied the existing calls.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can just leave it as is, I will note I plan to deprecate these functions in the next minor release because they cause a lot of confusion for users since they awkwards combine workflow and activities so I wouldn't spend a lot of time fussing over them.

@rodrigozhou rodrigozhou force-pushed the rodrigozhou/nexus-test-mocks branch from f6df7a0 to 3bb0bb9 Compare October 24, 2024 05:43
rodrigozhou and others added 2 commits October 28, 2024 14:09
* Support mocking Nexus operation with operation reference

* address comments

* address comments
@rodrigozhou rodrigozhou force-pushed the rodrigozhou/nexus-test-mocks branch from 606be16 to 509813d Compare October 28, 2024 21:09
@rodrigozhou rodrigozhou enabled auto-merge (squash) October 28, 2024 21:10
@rodrigozhou rodrigozhou merged commit 37d1775 into master Oct 28, 2024
14 checks passed
@rodrigozhou rodrigozhou deleted the rodrigozhou/nexus-test-mocks branch October 28, 2024 22:03
ReyOrtiz pushed a commit to ReyOrtiz/temporal-sdk-go that referenced this pull request Dec 5, 2024
* Support for mocking nexus operations

* address comments

* add tests

* add nexus events listeners

* add test for nexus listeners

* address comments

* add assert nexus calls methods

* address static checks

* Support mocking Nexus operation with operation reference (temporalio#1683)

* Support mocking Nexus operation with operation reference

* address comments

* address comments
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.

4 participants