Activity Response does not return correct value on call to OnActivity().Return()

Hi, I am trying to test workflow using testsuite.TestWorkflowEnvironment.

I am able to test activities for errors using OnActivity().Return() by returning errors. But when i use the similar OnActivity().Return() for response with nil error, i am not able to see the value from response in the actual workflow.

Activity Definition :

func S3ReadActivity(ctx context.Context, param S3ReadActivityParam) (S3ReadActivityResponse, error) {}

sample activity call in workflow :

var response S3ReadActivityResponse
err = workflow.ExecuteActivity(ctx, S3ReadActivity, S3ReadActivityParam{}).Get(ctx, &response)
if err != nil {
	workflow.GetLogger(ctx).Error("S3ReadActivity failed with ", err)
	return err
}

The above response does not match the value returned from the unit test OnActivity().Return()

Mocking used in unit test :

response := S3ReadActivityResponse{}
s.env.OnActivity(S3ReadActivity, mock.Anything, mock.Anything).Return(response, nil) 

What is the response or error happening? Is the test environment saying a mock doesn’t match? Or is it running another mock you registered? Or is it running the actual activity?

The response i am returning from OnActivity has subfields set with non empty values, but in the worfklow where i am logging the response after ExecuteActivity().Get() does not reflect that value.

I am afraid I am not able to replicate. Here is a simple example showing the mock value is properly used:

package scratch_test

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"
	"go.temporal.io/sdk/testsuite"
	"go.temporal.io/sdk/workflow"
)

type S3ReadActivityResponse struct{ SomeString string }

type S3ReadActivityParam struct{}

func S3ReadActivity(context.Context, S3ReadActivityParam) (S3ReadActivityResponse, error) {
	panic("Never called!")
}

func MyWorkflow(ctx workflow.Context) (string, error) {
	ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{ScheduleToCloseTimeout: 5 * time.Second})
	var response S3ReadActivityResponse
	err := workflow.ExecuteActivity(ctx, S3ReadActivity, S3ReadActivityParam{}).Get(ctx, &response)
	return response.SomeString, err
}

func TestWorkflow(t *testing.T) {
	var suite testsuite.WorkflowTestSuite
	env := suite.NewTestWorkflowEnvironment()
	env.RegisterWorkflow(MyWorkflow)
	env.OnActivity(S3ReadActivity, mock.Anything, mock.Anything).Return(S3ReadActivityResponse{SomeString: "Some Value"}, nil)
	env.ExecuteWorkflow(MyWorkflow)
	require.NoError(t, env.GetWorkflowError())
	var result string
	require.NoError(t, env.GetWorkflowResult(&result))
	require.Equal(t, "Some Value", result)
}

Does that help? If not, can you please provide a small, standalone set of code that shows what you are seeing?

Hi Chad, thanks a lot for quickly responding with such great example code. I have created sample version of my actual code which re-produces the problem i am facing in actual code. could you help me identify what am i doing wrong?

sample_activities.go

package temporal_workflow

import (
	"context"
)

type SampleActivityParam struct {
}

type SampleActivityResponse struct {
	orderIds []string
}

func SampleActivity(ctx context.Context, param ParseOrderLinesActivityParam) (SampleActivityResponse, error) {
	return SampleActivityResponse{}, nil
}

sample_workflow.go

package temporal_workflow

import (
	//"context"
	"time"

	"go.temporal.io/sdk/temporal"
	"go.temporal.io/sdk/workflow"
)

type MyWorkflowParam struct{}

func MyWorkflow(ctx workflow.Context) error {
	retryPolicy := temporal.RetryPolicy{
		InitialInterval:    1 * time.Second,
		BackoffCoefficient: 2,
		MaximumInterval:    1 * time.Minute,
		MaximumAttempts:    2,
	}
	ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{
		ScheduleToCloseTimeout: 5 * time.Second,
		//ScheduleToCloseTimeout: 5 * time.Second,
		//StartToCloseTimeout:    10 * time.Second,
		RetryPolicy: &retryPolicy,
	})
	var response SampleActivityResponse
	err := workflow.ExecuteActivity(ctx, SampleActivity, SampleActivityParam{}).Get(ctx, &response)
	//workflow.GetLogger(ctx).Info("Log workflow param", param)
	workflow.GetLogger(ctx).Info("Log response", response)
	return err
}

sample_workflow_test.go

package temporal_workflow

import (
	"testing"

	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/suite"
	"go.temporal.io/sdk/testsuite"
)

type SampleUnitTestSuite struct {
	suite.Suite
	testsuite.WorkflowTestSuite

	env *testsuite.TestWorkflowEnvironment
}

func (s *SampleUnitTestSuite) SetupTest() {
	s.env = s.NewTestWorkflowEnvironment()
	s.env.RegisterWorkflow(MyWorkflow)
	s.env.RegisterActivity(SampleActivity)
}

func (s *SampleUnitTestSuite) AfterTest(suiteName, testName string) {
	s.env.AssertExpectations(s.T())
}

func (s *SampleUnitTestSuite) Test_MyWorkflow_Success() {
	var strs []string
	strs = append(strs, "1", "2")
	response := SampleActivityResponse{orderIds: strs}
	s.env.OnActivity(SampleActivity, mock.Anything, mock.Anything).Return(response, nil)

	s.env.ExecuteWorkflow(MyWorkflow)
	s.True(s.env.IsWorkflowCompleted())
	s.NoError(s.env.GetWorkflowError())
}

func TestSampleUnitTestSuite(t *testing.T) {
	suite.Run(t, new(SampleUnitTestSuite))
}

Logs for running the tests:

2022/02/20 06:43:48 DEBUG handleActivityResult: *workflowservice.RespondActivityTaskCompletedRequest. ActivityID 1 ActivityType SampleActivity
2022/02/20 06:43:48 INFO  Log response {[]}
PASS
================================================================================
(12:13:48) INFO: Elapsed time: 5.255s, Critical Path: 4.96s
(12:13:48) INFO: 8 processes: 1 internal, 7 darwin-sandbox.
(12:13:48) INFO: Build completed successfully, 8 total actions
Test cases: finished with 0 passing and 0 failing out of 0 test cases

Executed 1 out of 1 test: 1 test passes.
(12:13:48) INFO: Build completed successfully, 8 total actions

Your issue is that you are not exporting orderIds so it is not serialized. Export it (i.e. capitalize it) and it will be serialized.

Thanks a lot Chad.

And I sincerely apologize for taking your time for my lack of golang proficiency.