Unit testing timeouts

Hi! I’m writing workflow unit tests and wondering if the Temporal experts here have any pointers on testing timeouts?
The workflow will be part of a service that has an SLO, so before executing a child workflow, I set a timeout:

	workflowOptions := workflow.ChildWorkflowOptions{
		WorkflowExecutionTimeout: time.Hour * 24,
	}

I want to test that this timeout indeed fails the workflow when running too long. In my test, I set it up so that child workflow runs a loooong time

s.env.OnWorkflow(ParentWorkflow, mock.Anything, param).Return(nil).After(30 * 24 * time.Hour).Once()
s.True(s.env.IsWorkflowCompleted())
s.NoError(s.env.GetWorkflowError())

I’m expecting this test to fail with the workflow returning a timeout error. But the test passed?! What am I doing wrong here?

Test framework performs time skipping if a workflow is blocked so it allows unit tests to execute much much faster.

You could use RegisterDelayedCallback (sample here) to registers a callback to fire after a delay and perform your checks in this callback.

That said we should try to reproduce this so could make sure there isn’t and issue with WorkflowExecutionTimeout in SDK test suite. Could you share a reproduce and your Go SDK version used?

I filed an issue to fix this.

Thank you tihomir! I was interested to know whether the issue was incorrect setup or that the timeouts are ignored in the test environment. Glad to have an answer :slight_smile:
I can probably do a manual testing to verify the timeout setup, but having a unit test would be much easier. Thanks maxim for filing the issue!
I’m using Go version 1.18.4

Repro:
Note that I made some changes to make the examples simpler but the results are the same.
Test:

type TimeoutTestSuite struct {
	suite.Suite
	testsuite.WorkflowTestSuite
	env *testsuite.TestWorkflowEnvironment
}

func (s *TimeoutTestSuite) SetupTest() {
	s.env = s.NewTestWorkflowEnvironment()
	s.env.RegisterWorkflow(NoOp)
	s.env.RegisterActivity(NoopActivity2)
	s.env.RegisterWorkflow(NoOp2)
	s.env.RegisterWorkflow(NoopChild)
}

func (s *TimeoutTestSuite) AfterTest(_, _ string) {
	s.env.AssertExpectations(s.T())
}

func TestTimeoutTestSuite(t *testing.T) {
	suite.Run(t, new(TimeoutTestSuite))
}

func (s *TimeoutTestSuite) Test_Timeouts_2() {
	// Make activity run longer than timeouts
	s.env.OnActivity(NoopActivity2, mock.Anything).Return(nil).After(30 * 24 * time.Hour).Once()

        // example 1: timeout set in test
	s.env.SetStartWorkflowOptions(client.StartWorkflowOptions{WorkflowExecutionTimeout: 1 * time.Hour})
	s.env.ExecuteWorkflow(NoOp)
	s.True(s.env.IsWorkflowCompleted())
	s.Error(s.env.GetWorkflowError()) // expect timeout error here but not the case
}

func (s *TimeoutTestSuite) Test_Timeouts_3() {
	// Make child workflows run longer than timeouts
	s.env.OnWorkflow(NoopChild, mock.Anything).Return(nil).After(30 * 24 * time.Hour).Once()

	s.env.ExecuteWorkflow(NoOp2)
	s.True(s.env.IsWorkflowCompleted())
	s.Error(s.env.GetWorkflowError()) // expect timeout error here but not the case
}

Workflow and activity:

func NoOp(ctx workflow.Context) error {
	wfOpts := workflow.GetChildWorkflowOptions(ctx)
	options := workflow.ActivityOptions{
		StartToCloseTimeout: wfOpts.WorkflowExecutionTimeout,
	}
	ctx = workflow.WithActivityOptions(ctx, options)
	if err := workflow.ExecuteActivity(ctx, NoopActivity2).Get(ctx, nil); err != nil {
		return err
	}
	return nil
}

func NoopActivity2(ctx context.Context) error {
	logger := activity.GetLogger(ctx)

	logger.Info("HELLO!")

	logger.Info("NoopActivity completed!")
	return nil
}

func NoOp2(ctx workflow.Context) error {
        // example 2: timeout set in workflow
	workflowOptions := workflow.ChildWorkflowOptions{
		WorkflowExecutionTimeout: 2 * time.Second,
	}
	ctx = workflow.WithChildOptions(ctx, workflowOptions)
	if err := workflow.ExecuteChildWorkflow(ctx, NoopChild).Get(ctx, nil); err != nil {
		return err
	}
	return nil
}

func NoopChild(ctx workflow.Context) error {
	return nil
}