Skip to content

MIGRATION ISSUE: replacement for awserr.New() when creating errors for unit tests #2857

Closed
@erikpaasonen

Description

@erikpaasonen

Pre-Migration Checklist

Go Version Used

Go 1.23

Describe the Migration Issue

Seeking the upgrade path for awserr.New() with respect to creating errors which can be unwrapped by errors.As() as recommended by the migration guide.

A simple, clear scenario is a function which creates a CloudWatch log group as needed. During certain well-understood race conditions the log group very well may exist by the time the code calls .CreateLogGroup(). I need to swallow the specific error condition when the log group already exists, and pass-through the error on all other error types. In order to unit test this function, I need to generate an error which emulates as perfectly as possible the actual error received at runtime.

In Go SDK v1 this was easy: awserr.New(). The migration guide's section on error handling as well as the Go SDK v2 error handling guide say that the Smithy-Go error wrapping is intended to completely replace the awserr package. I can find guidance on how to check these errors but unfortunately haven't found any yet on how to create or emulate them.

Code Comparison

Function snippet following the migration guide for error unwrapping and taking distinct action for a particular error code:

func (f FooStruct) MyCreateLogGrp(logGroupName string) (err error) {
  _, err = f.client.CreateLogGroup(context.TODO(), &cloudwatchlogs.CreateLogGroupInput{
    LogGroupName: aws.String(logGroupName),
  })
  if err != nil {
    var s smithy.APIError
    if errors.As(err, &s) {
      var raee *types.ResourceAlreadyExistsException
      if s.ErrorCode() == raee.ErrorCode() {
        log.Info("swallowing error")
      } else {
        log.Error("something happened")
      }
    } else {
      log.Error("did not satisfy smithy.APIError")
    }
  }
  return
}

also tried the simpler case to the CWL type directly, with effectively the same results:

func (f FooStruct) MyCreateLogGrp(logGroupName string) (err error) {
  _, err = f.client.CreateLogGroup(context.TODO(), &cloudwatchlogs.CreateLogGroupInput{
    LogGroupName: aws.String(logGroupName),
  })
  if err != nil {
    var raee *types.ResourceAlreadyExistsException
    if errors.As(err, raee) {
      log.Info("swallowing error")
    } else {
      log.Error("did not satisfy types.ResourceAlreadyExistsException")
    }
  }
  return
}

V1 test code which has worked fine:

func TestMyCreateLogGrp(t *testing.T) {  
  mockController := gomock.NewController(t)
  defer mockController.Finish()
  mockCloudWatchLogs := mockcwl.NewMockCloudWatchLogsAPI(mockController)
  mockCloudWatchLogs.EXPECT().CreateLogGroup(context.TODO(), gomock.Any()).Return(
    &cloudwatchlogs.CreateLogGroupOutput{},
    awserr.New(cloudwatchlogs.ErrCodeResourceAlreadyExistsException, "Log group already exists", nil),
  )
  ...
}

V2 test code returning a custom struct which ought to satisfy Smithy-Go, but unfortunately not working:

type mockApiError struct {
    ErrorCode string
    ErrorMsg  string
    Fault     smithy.ErrorFault
}

func TestMyCreateLogGrp(t *testing.T) {  
  mockController := gomock.NewController(t)
  defer mockController.Finish()
  mockCloudWatchLogs := mockcwlv2.NewMockCloudWatchLogsAPI(mockController)
  var raee types.ResourceAlreadyExistsException
  mockCloudWatchLogs.EXPECT().CreateLogGroup(context.TODO(), gomock.Any()).Return(
    &cloudwatchlogs.CreateLogGroupOutput{},
    mockApiError{
      ErrorCode: raee.ErrorCode(),
      ErrorMsg:  raee.ErrorMessage(),
      Fault:     raee.ErrorFault(),
    },
  )
  ...
}

also tried logging an SDK v2 error in an isolated environment, and copying-pasting its error string to errors.New():

func TestMyCreateLogGrp(t *testing.T) {  
  mockController := gomock.NewController(t)
  defer mockController.Finish()
  mockCloudWatchLogs := mockcwlv2.NewMockCloudWatchLogsAPI(mockController)
  mockCloudWatchLogs.EXPECT().CreateLogGroup(context.TODO(), gomock.Any()).Return(
    &cloudwatchlogs.CreateLogGroupOutput{},
    errors.New("operation error CloudWatch Logs: CreateLogGroup, https response error StatusCode: 400, RequestID: b978xxxx-xxxx-xxxx-xxxx-xxxx1ef4cf89, ResourceAlreadyExistsException: The specified log group already exists"),
  )
  ...
}

Observed Differences/Errors

In both of the above V2 variations, the code yields the log.Error("did not satisfy...") instead of the log.Info("swallowing error") as expected. Ultimately I'm no longer able to fully unit test this function with V2 now that awserr.New() has gone away.

Additional Context

No response

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions