Should Cancellation/Termination of Workflows Be Tested?

Would you recommend testing cancellation or termination of workflows? It looks like this would only be possible for activities since those can be mocked, but not so much for timers (like Sleep).

Perhaps, even broader - what do you think would be valuable unit tests for a fairly simple workflow?

Ex workflow:

  • Execute activity A (has retries, 1 non-retryable application error)
  • Sleep 2 days
  • Execute activity B (has retries, all errors retriable)

We do test termination as part of the Temporal service release. It doesn’t make sense to test termination from the application point of view as it doesn’t give any control to the application. It is like asking to test an application code when it receives “kill -9”.

You absolutely want to test workflow cancellation as it is expected that workflow code handles a cancellation request according to its business logic. You don’t need to mock Sleep to test cancellation. Canceling a workflow while it is sleeping is going to fail the Sleep call with CanceledError.

Perhaps, even broader - what do you think would be valuable unit tests for a fairly simple workflow?
Ex workflow:

  • Execute activity A (has retries, 1 non-retryable application error)
  • Sleep 2 days
  • Execute activity B (has retries, all errors retriable)

Not exhaustive list:

  • Test each activity individually using TestActivityEnvironment.
  • Test happy path mocking activities
  • Test A failure with non-retryable error
  • Test cancellation in each step

I couldn’t figure out how to do this - I figured out how to do it in the case of activities. Can I get a quick sample?

func cancelWorkflowTest(ctx Context) (string, error) {
	err := Sleep(ctx, 2 * time.Hour)
	if err != nil {
		return "", err
	}
	return "Completed.", nil
}

func (s *WorkflowUnitTest) Test_CancelWorkflow() {
	env := s.NewTestWorkflowEnvironment()
	env.RegisterDelayedCallback(func() {
		env.CancelWorkflow()
	}, time.Hour)
	env.ExecuteWorkflow(cancelWorkflowTest)
	s.NotNil(env.GetWorkflowError())
	err := errors.Unwrap(env.GetWorkflowError())
	_, ok := err.(*CanceledError)
	s.True(ok)
}

Related question: how should we be testing parent/child workflow cancellation policies?

I have a parent workflow and a child workflow. If the parent gets cancelled, the child should get a request for cancellation.

Example code:

...
	ctx = workflow.WithChildOptions(ctx, workflow.ChildWorkflowOptions{
		ParentClosePolicy: enums.PARENT_CLOSE_POLICY_REQUEST_CANCEL,
	})
	recurringWorkflowErr := workflow.ExecuteChildWorkflow(ctx, RecurringSubscription).Get(ctx, nil)
...

However, this doesn’t seem to work:

				env.OnWorkflow(subscriptions.RecurringSubscription, mock.Anything).Run(func(args mock.Arguments) {
					env.CancelWorkflow()
				})

				env.ExecuteWorkflow(subscriptions.Subscription)
				Expect(env.IsWorkflowCompleted()).To(BeTrue())
				Expect(env.GetWorkflowError()).To(HaveOccurred())
				Expect(temporal.IsCanceledError(env.GetWorkflowError())).To(BeTrue()) // Fails here

There is an error, but it appears to be related to mocking “mock of RecurringSubscription has no returns”. Am I doing something wrong?

Would you file an issue against sdk-go to get this looked at?

Filed: [Testing] Cancellation of Parent Workflow with Child Workflow Panics · Issue #351 · temporalio/sdk-go · GitHub