Why does a mocked Temporal child workflow fail to receive signals in golang SDK?

When running Temporal’s golang SDK (v1.35.0) to mock a parent and child workflow, only the first signal sent by the parent is successfully delivered to the child workflow; all subsequent signals are not received.

A minimal example is shown below. (Note that it’s not possible to use env.OnSignalExternalWorkflow here, because “TestWorkflowEnvironment handles sending signals between the workflows that are started from the root workflow. For example, sending signals between parent and child workflows. Or sending signals between 2 child workflows”, per the docs.)

func TestParentChildSignalExchange(t *testing.T) {
    var ts testsuite.WorkflowTestSuite
    env := ts.NewTestWorkflowEnvironment()
    var childReceivedC bool

    mockChildWorkflow := func(ctx workflow.Context, parentWfId string) error {
        s := workflow.NewSelector(ctx)
        s.AddReceive(workflow.GetSignalChannel(ctx, SignalA), func(c workflow.ReceiveChannel, _ bool) {
            var val string
            c.Receive(ctx, &val)
            fmt.Println("Child received signal A")
            _ = workflow.SignalExternalWorkflow(ctx, parentWfId, "", SignalB, "payload-B").Get(ctx, nil)
            fmt.Println("Child sent signal B")
        })

        s.AddReceive(workflow.GetSignalChannel(ctx, SignalC), func(c workflow.ReceiveChannel, _ bool) {
            var val string
            c.Receive(ctx, &val)
            fmt.Println("Child received signal C")
            childReceivedC = true
        })

        // Selects for signal A and signal C
        s.Select(ctx)
        s.Select(ctx)
        return nil
    }
    parentWorkflow := func(ctx workflow.Context) error {
        cwo := workflow.ChildWorkflowOptions{
            WorkflowID: "child-workflow-id",
        }
        var childWE workflow.Execution
        workflowId := workflow.GetInfo(ctx).WorkflowExecution.ID
        ctx = workflow.WithChildOptions(ctx, cwo)
        err := workflow.ExecuteChildWorkflow(ctx, "MockChildWorkflow", workflowId).GetChildWorkflowExecution().Get(ctx, &childWE)
        if err != nil {
            return err
        }

        workflow.SignalExternalWorkflow(ctx, childWE.ID, childWE.RunID, SignalA, "payload-A")
        fmt.Println("Parent sent signal A")
        var bVal string
        s := workflow.NewSelector(ctx)
        s.AddReceive(workflow.GetSignalChannel(ctx, SignalB), func(c workflow.ReceiveChannel, _ bool) {
            c.Receive(ctx, &bVal)
            fmt.Println("Parent received signal B")
            _ = workflow.SignalExternalWorkflow(ctx, childWE.ID, childWE.RunID, SignalC, "payload-C").Get(ctx, nil)
            fmt.Println("Parent sent signal C")
        })
        s.Select(ctx)
        return nil
    }
    env.RegisterWorkflow(parentWorkflow)
    env.RegisterWorkflowWithOptions(mockChildWorkflow, workflow.RegisterOptions{Name: "MockChildWorkflow"})

    env.ExecuteWorkflow(parentWorkflow)
    require.True(t, env.IsWorkflowCompleted())
    require.True(t, childReceivedC, "child workflow should have received signal C")
}

This gives the following output, showing that the child workflow successfully receives the first signal but not the second.

=== RUN   TestParentChildSignalExchange
2025/08/27 17:34:10 INFO  ExecuteChildWorkflow WorkflowType MockChildWorkflow
Parent sent signal A
Child received signal A
Child sent signal B
Parent received signal B
Parent sent signal C
    /home/patrick/go-scheduler/workflow/temporal_executor_test.go:158:
                Error Trace:    /home/patrick/go-scheduler/workflow/temporal_executor_test.go:158
                Error:          Should be true
                Test:           TestParentChildSignalExchange
                Messages:       child workflow should have received signal C
--- FAIL: TestParentChildSignalExchange (0.00s)
FAIL

What is the cause of this behavior? Many thanks.

Hi ,

I think the parent workflow is completing before the child receive the signal. One way of fixing it is waiting for the child workflow to complete (before closing the parent)

Instead of

        err := workflow.ExecuteChildWorkflow(ctx, "MockChildWorkflow", workflowId).GetChildWorkflowExecution().Get(ctx, &childWE)

do


		// Get the child workflow execution handle
		childWorkflowFuture := workflow.ExecuteChildWorkflow(ctx, "MockChildWorkflow", workflowId)

		// A Get the child workflow execution details
		err := childWorkflowFuture.GetChildWorkflowExecution().Get(ctx, &childWE)
		if err != nil {
			return err
		}

and before completing the parent wait for the child to complete

     parentWorkflow := func(ctx workflow.Context) error {

      .....
      .....
      .....
      .....
		// Wait for the child workflow to complete
		var childResult interface{}
		err = childWorkflowFuture.Get(ctx, &childResult)
		if err != nil {
			return err
		}

		return nil
	}