Skip to content

Commit

Permalink
Improve Update Workflow testing UX (#1721)
Browse files Browse the repository at this point in the history
* default implementation for UpdateCallbacks and UpdateWorkflowNoRejection for ergonomics

* expose TestUpdateCallback externally

* OnAccept, OnReject, OnComplete, tested with samples-go
  • Loading branch information
yuandrew authored Nov 25, 2024
1 parent 99a35d4 commit c7fa7e8
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 99 deletions.
20 changes: 1 addition & 19 deletions internal/internal_workflow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1402,24 +1402,6 @@ func (s *WorkflowUnitTest) Test_MutatingFunctionsInQueries() {
s.NoError(env.GetWorkflowError())
}

type updateCallback struct {
accept func()
reject func(error)
complete func(interface{}, error)
}

func (uc *updateCallback) Accept() {
uc.accept()
}

func (uc *updateCallback) Reject(err error) {
uc.reject(err)
}

func (uc *updateCallback) Complete(success interface{}, err error) {
uc.complete(success, err)
}

func (s *WorkflowUnitTest) Test_MutatingFunctionsInUpdateValidator() {
env := s.NewTestWorkflowEnvironment()

Expand All @@ -1438,7 +1420,7 @@ func (s *WorkflowUnitTest) Test_MutatingFunctionsInUpdateValidator() {
}
env.RegisterWorkflow(wf)
env.RegisterDelayedCallback(func() {
env.UpdateWorkflow(updateType, "testID", &updateCallback{})
env.UpdateWorkflow(updateType, "testID", &TestUpdateCallback{})
}, time.Second)
env.ExecuteWorkflow(wf)
s.True(env.IsWorkflowCompleted())
Expand Down
33 changes: 33 additions & 0 deletions internal/workflow_testsuite.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (

"github.com/nexus-rpc/sdk-go/nexus"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
commonpb "go.temporal.io/api/common/v1"
enumspb "go.temporal.io/api/enums/v1"

Expand Down Expand Up @@ -83,6 +84,15 @@ type (
runFn func(args mock.Arguments)
waitDuration func() time.Duration
}

// TestUpdateCallback is a basic implementation of the UpdateCallbacks interface for testing purposes.
// Tests are welcome to implement their own version of this interface if they need to test more complex
// update logic. This is a simple implementation to make testing basic Workflow Updates easier.
TestUpdateCallback struct {
OnAccept func()
OnReject func(error)
OnComplete func(interface{}, error)
}
)

func newEncodedValues(values *commonpb.Payloads, dc converter.DataConverter) converter.EncodedValues {
Expand Down Expand Up @@ -767,6 +777,18 @@ func (c *MockCallWrapper) NotBefore(calls ...*MockCallWrapper) *MockCallWrapper
return c
}

func (uc *TestUpdateCallback) Accept() {
uc.OnAccept()
}

func (uc *TestUpdateCallback) Reject(err error) {
uc.OnReject(err)
}

func (uc *TestUpdateCallback) Complete(success interface{}, err error) {
uc.OnComplete(success, err)
}

// ExecuteWorkflow executes a workflow, wait until workflow complete. It will fail the test if workflow is blocked and
// cannot complete within TestTimeout (set by SetTestTimeout()).
func (e *TestWorkflowEnvironment) ExecuteWorkflow(workflowFn interface{}, args ...interface{}) {
Expand Down Expand Up @@ -1079,6 +1101,17 @@ func (e *TestWorkflowEnvironment) UpdateWorkflowByID(workflowID, updateName, upd
return e.impl.updateWorkflowByID(workflowID, updateName, updateID, uc, args)
}

func (e *TestWorkflowEnvironment) UpdateWorkflowNoRejection(updateName string, updateID string, t mock.TestingT, args ...interface{}) {
uc := &TestUpdateCallback{
OnReject: func(err error) {
require.Fail(t, "update should not be rejected")
},
OnAccept: func() {},
OnComplete: func(interface{}, error) {},
}
e.UpdateWorkflow(updateName, updateID, uc, args)
}

// QueryWorkflowByID queries a child workflow by its ID and returns the result synchronously
func (e *TestWorkflowEnvironment) QueryWorkflowByID(workflowID, queryType string, args ...interface{}) (converter.EncodedValue, error) {
return e.impl.queryWorkflowByID(workflowID, queryType, args...)
Expand Down
106 changes: 26 additions & 80 deletions internal/workflow_testsuite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,12 +274,12 @@ func TestWorkflowIDUpdateWorkflowByID(t *testing.T) {
// Test UpdateWorkflowByID works with custom ID
env := suite.NewTestWorkflowEnvironment()
env.RegisterDelayedCallback(func() {
err := env.UpdateWorkflowByID("my-workflow-id", "update", "id", &updateCallback{
reject: func(err error) {
err := env.UpdateWorkflowByID("my-workflow-id", "update", "id", &TestUpdateCallback{
OnReject: func(err error) {
require.Fail(t, "update should not be rejected")
},
accept: func() {},
complete: func(interface{}, error) {},
OnAccept: func() {},
OnComplete: func(interface{}, error) {},
}, "input")
require.NoError(t, err)
}, time.Second)
Expand Down Expand Up @@ -311,13 +311,13 @@ func TestChildWorkflowUpdate(t *testing.T) {
ID: wfID,
})
env.RegisterDelayedCallback(func() {
err := env.UpdateWorkflowByID("child-workflow", "child-handler", "1", &updateCallback{
accept: func() {
err := env.UpdateWorkflowByID("child-workflow", "child-handler", "1", &TestUpdateCallback{
OnAccept: func() {
},
reject: func(err error) {
OnReject: func(err error) {
require.Fail(t, "update failed", err)
},
complete: func(result interface{}, err error) {
OnComplete: func(result interface{}, err error) {
if err != nil {
require.Fail(t, "update failed", err)
}
Expand Down Expand Up @@ -369,13 +369,7 @@ func TestWorkflowUpdateOrder(t *testing.T) {
// Test UpdateWorkflowByID works with custom ID
env := suite.NewTestWorkflowEnvironment()
env.RegisterDelayedCallback(func() {
env.UpdateWorkflow("update", "id", &updateCallback{
reject: func(err error) {
require.Fail(t, "update should not be rejected")
},
accept: func() {},
complete: func(interface{}, error) {},
})
env.UpdateWorkflowNoRejection("update", "id", t)
}, 0)

env.ExecuteWorkflow(func(ctx Context) (int, error) {
Expand Down Expand Up @@ -407,14 +401,14 @@ func TestWorkflowNotRegisteredRejected(t *testing.T) {
env := suite.NewTestWorkflowEnvironment()
var updateRejectionErr error
env.RegisterDelayedCallback(func() {
env.UpdateWorkflow("update", "id", &updateCallback{
reject: func(err error) {
env.UpdateWorkflow("update", "id", &TestUpdateCallback{
OnReject: func(err error) {
updateRejectionErr = err
},
accept: func() {
OnAccept: func() {
require.Fail(t, "update should not be accepted")
},
complete: func(interface{}, error) {},
OnComplete: func(interface{}, error) {},
})
}, 0)

Expand All @@ -433,36 +427,24 @@ func TestWorkflowUpdateOrderAcceptReject(t *testing.T) {
env := suite.NewTestWorkflowEnvironment()
// Send 3 updates, with one bad update
env.RegisterDelayedCallback(func() {
env.UpdateWorkflow("update", "1", &updateCallback{
reject: func(err error) {
require.Fail(t, "update should not be rejected")
},
accept: func() {},
complete: func(interface{}, error) {},
})
env.UpdateWorkflowNoRejection("update", "1", t)
}, 0)

var updateRejectionErr error
env.RegisterDelayedCallback(func() {
env.UpdateWorkflow("bad update", "2", &updateCallback{
reject: func(err error) {
env.UpdateWorkflow("bad update", "2", &TestUpdateCallback{
OnReject: func(err error) {
updateRejectionErr = err
},
accept: func() {
require.Fail(t, "update should not be rejected")
OnAccept: func() {
require.Fail(t, "update should not be accepted")
},
complete: func(interface{}, error) {},
OnComplete: func(interface{}, error) {},
})
}, 0)

env.RegisterDelayedCallback(func() {
env.UpdateWorkflow("update", "3", &updateCallback{
reject: func(err error) {
require.Fail(t, "update should not be rejected")
},
accept: func() {},
complete: func(interface{}, error) {},
})
env.UpdateWorkflowNoRejection("update", "3", t)
}, 0)

env.ExecuteWorkflow(func(ctx Context) (int, error) {
Expand Down Expand Up @@ -496,23 +478,11 @@ func TestAllHandlersFinished(t *testing.T) {
env := suite.NewTestWorkflowEnvironment()

env.RegisterDelayedCallback(func() {
env.UpdateWorkflow("update", "id_1", &updateCallback{
reject: func(err error) {
require.Fail(t, "update should not be rejected")
},
accept: func() {},
complete: func(interface{}, error) {},
})
env.UpdateWorkflowNoRejection("update", "id_1", t)
}, 0)

env.RegisterDelayedCallback(func() {
env.UpdateWorkflow("update", "id_2", &updateCallback{
reject: func(err error) {
require.Fail(t, "update should not be rejected")
},
accept: func() {},
complete: func(interface{}, error) {},
})
env.UpdateWorkflowNoRejection("update", "id_2", t)
}, time.Minute)

env.ExecuteWorkflow(func(ctx Context) (int, error) {
Expand Down Expand Up @@ -570,33 +540,15 @@ func TestWorkflowAllHandlersFinished(t *testing.T) {
env := suite.NewTestWorkflowEnvironment()

env.RegisterDelayedCallback(func() {
env.UpdateWorkflow("update", "id_1", &updateCallback{
reject: func(err error) {
require.Fail(t, "update should not be rejected")
},
accept: func() {},
complete: func(interface{}, error) {},
})
env.UpdateWorkflowNoRejection("update", "id_1", t)
}, 0)

env.RegisterDelayedCallback(func() {
env.UpdateWorkflow("update", "id_2", &updateCallback{
reject: func(err error) {
require.Fail(t, "update should not be rejected")
},
accept: func() {},
complete: func(interface{}, error) {},
})
env.UpdateWorkflowNoRejection("update", "id_2", t)
}, time.Minute)

env.RegisterDelayedCallback(func() {
env.UpdateWorkflow("nonWarningHandler", "id_3", &updateCallback{
reject: func(err error) {
require.Fail(t, "update should not be rejected")
},
accept: func() {},
complete: func(interface{}, error) {},
})
env.UpdateWorkflowNoRejection("nonWarningHandler", "id_3", t)
}, 2*time.Minute)

env.RegisterDelayedCallback(func() {
Expand Down Expand Up @@ -727,13 +679,7 @@ func TestWorkflowUpdateLogger(t *testing.T) {
env := suite.NewTestWorkflowEnvironment()

env.RegisterDelayedCallback(func() {
env.UpdateWorkflow("logging_update", "id_1", &updateCallback{
reject: func(err error) {
require.Fail(t, "update should not be rejected")
},
accept: func() {},
complete: func(interface{}, error) {},
})
env.UpdateWorkflowNoRejection("logging_update", "id_1", t)
}, 0)

env.RegisterDelayedCallback(func() {
Expand Down
3 changes: 3 additions & 0 deletions testsuite/testsuite.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ type (

// MockCallWrapper is a wrapper to mock.Call. It offers the ability to wait on workflow's clock instead of wall clock.
MockCallWrapper = internal.MockCallWrapper

// TestUpdateCallback is a basic implementation of the UpdateCallbacks interface for testing purposes.
TestUpdateCallback = internal.TestUpdateCallback
)

// ErrMockStartChildWorkflowFailed is special error used to indicate the mocked child workflow should fail to start.
Expand Down

0 comments on commit c7fa7e8

Please sign in to comment.