Unit Testing: Problems Using OnWorkflow

Hi,

I think I’m using OnWorkflow incorrectly. Could someone enlighten me please? Thanks!

func SimpleWorkflow(ctx workflow.Context, u []uuid.UUID) error {
	if workflow.GetInfo(ctx).ContinuedExecutionRunID == "" {
		if err := workflow.ExecuteActivity(workflow.WithActivityOptions(ctx, workflow.ActivityOptions{StartToCloseTimeout: 10 * time.Minute}), SimpleActivity).Get(ctx, &u); err != nil {
			return err
		}
	}

	for i, p := range u {
		if i > 10000 {
			return workflow.NewContinueAsNewError(ctx, SimpleWorkflow, u[i:])
		}
		workflow.ExecuteChildWorkflow(ctx, SimpleChildWorkflow, p)
	}
	return nil
}

func SimpleActivity(ctx context.Context) ([]uuid.UUID, error) {
	return nil, nil
}

func SimpleChildWorkflow(ctx workflow.Context, u uuid.UUID) error {
	return nil
}
type SimpleWorkflowUnitTestSuite struct {
	suite.Suite
	testsuite.WorkflowTestSuite
}

func (s *SimpleWorkflowUnitTestSuite) TestSimple() {
	env := s.NewTestWorkflowEnvironment()
	env.RegisterWorkflow(SimpleWorkflow)
	env.RegisterWorkflow(SimpleChildWorkflow)
	env.OnActivity(SimpleActivity, mock.Anything).Return(make([]uuid.UUID, 20000), nil).Once()
	count := 0
	env.OnWorkflow(SimpleChildWorkflow, mock.Anything, mock.Anything).Return(
		func(_ workflow.Context, _ uuid.UUID) error {
			count++
			return nil
		}).Times(10000)
	env.ExecuteWorkflow(SimpleWorkflow, nil)
	s.True(env.IsWorkflowCompleted())
	s.True(workflow.IsContinueAsNewError(env.GetWorkflowError()))
	s.Equal(10000, count) // <<<<<<<<< this fails
	env.AssertExpectations(s.T())
}

func TestSimpleWorkflowUnitTestSuite(t *testing.T) {
	suite.Run(t, new(SimpleWorkflowUnitTestSuite))
}

Unexpectedly results in count being 0. How do I set this up such that the mock of SimpleChildWorkflow counts the number of times it was called?

Thanks!

Albert

Imo you just do not need to register SimpleChildWorkflow in the test. Though I did not try this in action :slightly_smiling_face:

Mmm, when that line is removed, the code panics with:

unable to find workflow type: SimpleChildWorkflow. Supported types: [SimpleWorkflow]

Use it like this: env.OnWorkflow("SimpleChildWorkflow", .... It is testyfy-like, so…

It seems that all those things you register before are things that will be used in a test as real implementations.

The code still panics.

//env.RegisterWorkflow(SimpleChildWorkflow)
env.OnWorkflow("SimpleChildWorkflow", mock.Anything, mock.Anything).Return(
panic: unable to find workflow type: SimpleChildWorkflow. Supported types: [SimpleWorkflow]

You never wait on a child workflow, so the workflow just completes immediately. You should at least confirm the child workflow got started without error much less actually runs without error. Something like this:

func SimpleWorkflow(ctx workflow.Context, u []uuid.UUID) error {
	if workflow.GetInfo(ctx).ContinuedExecutionRunID == "" {
		if err := workflow.ExecuteActivity(workflow.WithActivityOptions(ctx, workflow.ActivityOptions{StartToCloseTimeout: 10 * time.Minute}), SimpleActivity).Get(ctx, &u); err != nil {
			return err
		}
	}

	// Only do the first N, then continue as new with the rest
	const amountToHandle = 5
	toHandle := u
	if len(toHandle) > amountToHandle {
		toHandle = toHandle[:amountToHandle]
	}
	selector := workflow.NewSelector(ctx)
	var err error

	// Start the children
	for _, p := range toHandle {
		selector.AddFuture(workflow.ExecuteChildWorkflow(ctx, SimpleChildWorkflow, p), func(f workflow.Future) {
			err = f.Get(ctx, nil)
		})
	}

	// Wait for all children to complete successfully
	for range toHandle {
		selector.Select(ctx)
		if err != nil {
			return err
		}
	}

	// Return continue as new if there are more
	if len(u) > amountToHandle {
		return workflow.NewContinueAsNewError(ctx, SimpleWorkflow, u[amountToHandle:])
	}
	return nil
}

You probably shouldn’t run 10k child workflows like this in a single run due to history size limits and input param limits. You should probably batch it up a bit differently on the caller side.

Silly question, is there a way to wait until the child workflow starts? I don’t want to block until all the children workflow have completed.

Absolutely, see workflow package - go.temporal.io/sdk/workflow - Go Packages. Specifically the future has a getter for a “start” future on it that returns the execution

if err := future.GetChildWorkflowExecution().Get(ctx, nil); err != nil {
	// Problem starting workflow.
	return err
}

Just to add, this forum post has good info on starting child async.

Perhaps I’m not reading the documentation correctly.

The Get() method will block until the workflow completes and results are available.

from: workflow package - go.temporal.io/sdk/workflow - Go Packages

Doesn’t that mean that the parent workflow will wait until the child workflow is completed?

I’ve also verified in code that Get() will block until the child workflow is complete.

Update: Ah, nevermind. I missed the GetChildWorkflowExecution().

Doesn’t that mean that the parent workflow will wait until the child workflow is completed?

Not sure I understand the question exactly. Basically if you call “Future.Get()” in a workflow, it waits for “Get()” to complete. If that “Get()” is on a child future, it will wait for the child workflow to complete. If you want to wait for the child workflow to start, use “GetChildWorkflowExecution().Get()” on the child future.

1 Like