awsmocker

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 11, 2026 License: MIT Imports: 42 Imported by: 0

README

AWS Mocker for Go

godoc License Go Report Card

Easily create a proxy to allow easy testing of AWS API calls.

⚠ This is considered alpha quality right now. It might not work for all of AWS's APIs.

[!IMPORTANT]
Version 1.0.0 has BREAKING CHANGES:

  • You must use the AWS config from Config() function returned by Start().
  • The Start function has been modified to accept variable arguments for options setting

If you find problems, please create an Issue or make a PR.

Installation

go get -u github.com/webdestroya/awsmocker

Configuration

The default configuration will setup a few mocks for STS.

m := awsmocker.Start(t)

For advanced usage and adding other mocks, you can use the following options:

m := awsmocker.Start(t, ...OPTIONS)

Defining Mocks

&awsmocker.MockedEndpoint{
  Request:  &awsmocker.MockedRequest{},
  Response: &awsmocker.MockedResponse{},
}

Mocking Requests
Key Type Description
Service string The AWS shortcode/subdomain for the service. (ec2, ecs, iam, dynamodb, etc)
Action string The AWS action name that is being mocked. (DescribeSubnets, ListClusters, AssumeRole, etc)
Params url.Values Matches against POST FORM PARAMs. This is only useful for older XML style API requests. This will not match against newer JSON requests.
Method string Uppercase string of the HTTP method to match against
Path string Matches the request path
PathRegex string Matches the request path using regex
IsEc2IMDS bool If set to true, then will match against the IPv4 and IPv6 hostname for EC2 IMDS
JMESPathMatches map[string]any A map of JMESpath expressions with their expected values. This will be matched against the JSON payload.
Matcher func(*ReceivedRequest) bool A custom function that you can use to do any complex logic you want. This is run after the other matchers, so you can use them to filter down requests before they hit your matcher.
MaxMatchCount int If this is greater than zero, then this mock will stop matching after it reaches the provided number of matches. This is useful for doing waiters.
Hostname string Matches a specific hostname. This is normally not recommended unless you are mocking non-AWS services.
Body string This matches a body of a request verbatim. This is not recommend unless you want to exactly match a request.
Strict bool This is only relevant if you provided Params. If strict mode is on, then the parameters much match entirely (and only) the provided parameter set.
Mocking Responses
Key Type Description
Body SEE BELOW SEE BELOW
StatusCode int Default 200. Allows you to override the status code for this response
ContentType string Allows overriding the content type header. By default this is handled for you based on the request
Encoding ResponseEncoding Allows you to force how the body will be encoded. By default, requests that use newer API style will receive JSON responses, and older request styles will get an XML document.
DoNotWrap bool Prevents wrapping XML responses with ACTIONResponse>ACTIONResult. Set this to true if you are doing some custom XML
RootTag string If you are doing custom XML responses, they will need a wrapping parent tag. This is where you specify the name.
Handler func(*ReceivedRequest) *http.Response If you want to handle the request entirely on your own, you can provide a function that will be passed the request and you can return an HTTP Response

Specifying Response Body:

Body variable type Description
string Default. Body will be returned as the string verbatim
func(*ReceivedRequest) (string) The function will be executed and the resulting string will be returned with 200OK and a default content type.
func(*ReceivedRequest) (string, int) Same as above, but with custom status code
func(*ReceivedRequest) (string, int, string) Same as above, but with custom content type.
map or struct Will be encoded into either JSON or XML depending on the request.

Usage

func TestSomethingThatCallsAws(t *testing.T) {
  m := awsmocker.Start(t, awsmocker.WithMocks(
      // Simple construction of a response
      awsmocker.NewSimpleMockedEndpoint("sts", "GetCallerIdentity", sts.GetCallerIdentityOutput{
        Account: aws.String("123456789012"),
        Arn:     aws.String("arn:aws:iam::123456789012:user/fakeuser"),
        UserId:  aws.String("AKIAI44QH8DHBEXAMPLE"),
      }),

      // advanced construction
      &awsmocker.MockedEndpoint{
        Request: &awsmocker.MockedRequest{
          // specify the service/action to respond to
          Service: "ecs",
          Action:  "DescribeServices",
        },
        // provide the response to give
        Response: &awsmocker.MockedResponse{
          Body: map[string]any{
            "services": []map[string]any{
              {
                "serviceName": "someservice",
              },
            },
          },
        },
      },
    ),
  )

  stsClient := sts.NewFromConfig(m.Config())

  resp, err := stsClient.GetCallerIdentity(context.TODO(), nil)
  if err != nil {
    t.Errorf("Error STS.GetCallerIdentity: %s", err)
    return
  }

  if *resp.Account != "123456789012" {
    t.Errorf("AccountID Mismatch: %v", *resp.Account)
  }

  // ... do the rest of your test here
}
Dynamic Response
func Mock_Events_PutRule_Generic() *awsmocker.MockedEndpoint {
  return &awsmocker.MockedEndpoint{
    Request: &awsmocker.MockedRequest{
      Service: "events",
      Action:  "PutRule",
    },
    Response: &awsmocker.MockedResponse{
      Body: func(rr *awsmocker.ReceivedRequest) string {

        name, _ := jmespath.Search("Name", rr.JsonPayload)

        return util.Must(util.Jsonify(map[string]any{
          "RuleArn": fmt.Sprintf("arn:aws:events:%s:%s:rule/%s", rr.Region, awsmocker.DefaultAccountId, name.(string)),
        }))
      },
    },
  }
}

Viewing Requests/Responses

To see the request/response traffic, you can use either of the following:

  • Set awsmocker.GlobalDebugMode = true in your tests
  • Use the AWSMOCKER_DEBUG=true environment variable

Assumptions/Limitations

  • The first matching mock is returned.
  • Service is assumed by the credential header
  • Action is calculated by the Action parameter, or the X-amz-target header.
  • if you provide a response object, it will be encoded to JSON or XML based on the requesting content type. If you need a response in a special format, please provide the content type and a string for the body.
  • There is very little "error handling". If something goes wrong, it just panics. This might be less than ideal, but the only usecase for this library is within a test, which would make the test fail. This is the goal.

Possible Issues

Receiving error: "not found, ResolveEndpointV2":
Upgrade aws modules: go get -u github.com/aws/aws-sdk-go-v2/...

See Also

Documentation

Overview

Package awsmocker allows easier mocking of AWS API responses.

Example Usage

The following is a complete example using awsmocker in an example test:

import (
  "testing"
	"context"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/ecs"
  "github.com/webdestroya/awsmocker"
)

func TestEcsDescribeServices(t *testing.T) {
	m := awsmocker.Start(t, awsmocker.WithMocks(&awsmocker.MockedEndpoint{
		Request: &awsmocker.MockedRequest{
			Service: "ecs",
			Action:  "DescribeServices",
		},
		Response: &awsmocker.MockedResponse{
			Body: map[string]any{
				"services": []map[string]any{
					{
						"serviceName": "someservice",
					},
				},
			},
		},
	}))

	client := ecs.NewFromConfig(m.Config())

		resp, err := client.DescribeServices(context.TODO(), &ecs.DescribeServicesInput{
			Services: []string{"someservice"},
			Cluster:  aws.String("testcluster"),
		})
		if err != nil {
			t.Errorf(err)
		}
		if *resp.Services[0].ServiceName != "someservice" {
			t.Errorf("Service name was wrong")
		}
	}

Index

Constants

View Source
const (
	ContentTypeXML  = "text/xml"
	ContentTypeJSON = "application/x-amz-json-1.1"
	ContentTypeText = "text/plain"
)
View Source
const (
	DefaultAccountId = "555555555555"
	DefaultRegion    = "us-east-1"
)

Variables

View Source
var (
	// Will Print out all the Request/Response traffic from the proxy
	GlobalDebugMode = false

	// where debugging output will go if requested
	DebugOutputWriter io.Writer = os.Stdout
)
View Source
var (
	// Default Mock for the sts:GetCallerIdentity request
	MockStsGetCallerIdentityValid = &MockedEndpoint{
		Request: &MockedRequest{
			Service: "sts",
			Action:  "GetCallerIdentity",
		},
		Response: &MockedResponse{
			StatusCode: http.StatusOK,
			Encoding:   ResponseEncodingXML,
			Body: map[string]any{
				"Account": DefaultAccountId,
				"Arn":     fmt.Sprintf("arn:aws:iam::%s:user/fakeuser", DefaultAccountId),
				"UserId":  "AKIAI44QH8DHBEXAMPLE",
			},
		},
	}
)

Functions

func CACert

func CACert() *x509.Certificate

Returns the parsed X509 Certificate

func CACertPEM

func CACertPEM() []byte

Exports the PEM Bytes of the CA Certificate (if you need to use it)

func EncodeAsJson added in v0.2.0

func EncodeAsJson(obj any) string

JSONifies the given object

func JMESMatch added in v0.2.5

func JMESMatch(obj any, expression string, expected any) bool

Performs a JMES Expression match on the object. The expected value should be a scalar or a function that takes a single any and returns a boolean the value returned from the jmes expression should equal the expected value all numerical values will be casted to a float64 (as that is what json numbers are treated as)

Types

type AwsLoadOptionsFunc added in v1.0.0

type AwsLoadOptionsFunc = func(*config.LoadOptions) error

Come on Amazon... Can't use {config.LoadOptionsFunc} because that is not a param for LoadDefaultConfig Why would you define the type and then not even use it...

type IMDSMockOptionFunc added in v1.0.0

type IMDSMockOptionFunc = func(*IMDSMockOptions)

type IMDSMockOptions added in v0.2.0

type IMDSMockOptions struct {
	// The identity document to return
	IdentityDocument imds.InstanceIdentityDocument

	// any custom user data
	UserData string

	// if you want to override the role name that is used for EC2 creds
	RoleName string

	// Override the instance profile name
	InstanceProfileName string
}

Override the default settings when using a default IMDS mock

type MockedEndpoint

type MockedEndpoint struct {
	Request  *MockedRequest
	Response *MockedResponse
}

func Mock_Failure added in v0.2.5

func Mock_Failure(service, action string) *MockedEndpoint

Returns an error response for a given service/action call

func Mock_Failure_WithCode added in v0.2.5

func Mock_Failure_WithCode(statusCode int, service, action, errorCode, errorMessage string) *MockedEndpoint

Mocks a specific Service:Action call to return an error

func Mock_IMDS_API_Token added in v0.2.0

func Mock_IMDS_API_Token() *MockedEndpoint

func Mock_IMDS_Common added in v0.2.0

func Mock_IMDS_Common(optFns ...IMDSMockOptionFunc) []*MockedEndpoint

Provides an array of mocks that will provide a decent replication of the EC2 Instance Metadata Service

func Mock_IMDS_IAM_Credentials added in v0.2.0

func Mock_IMDS_IAM_Credentials(roleName string) *MockedEndpoint

func Mock_IMDS_IAM_Info added in v0.2.0

func Mock_IMDS_IAM_Info(profileName string) *MockedEndpoint

func Mock_IMDS_IAM_RoleList added in v0.2.0

func Mock_IMDS_IAM_RoleList(roleName string) *MockedEndpoint

func Mock_IMDS_IdentityDocument added in v0.2.0

func Mock_IMDS_IdentityDocument(optFns ...func(*imds.InstanceIdentityDocument)) *MockedEndpoint

Provide a document to be returned, or nil to use a default one

func Mock_IMDS_MetaData_KeyValue added in v0.2.0

func Mock_IMDS_MetaData_KeyValue(k, v string) *MockedEndpoint

func Mock_IMDS_UserData added in v0.2.0

func Mock_IMDS_UserData(userData string) *MockedEndpoint

func NewSimpleMockedEndpoint

func NewSimpleMockedEndpoint(service, action string, responseObj any) *MockedEndpoint

Generates a simple MockedEndpoint for the Service:Action

type MockedRequest

type MockedRequest struct {
	// Require that fields are matched exactly
	//
	// Nonstrict (default) means that Params listed are matched against
	// the request to ensure the ones specified match
	//
	// Strict mode requires that the request contain ONLY the params listed
	// any extra parameters will cause the request to fail to match
	Strict bool

	// The hostname only. Does not include the port
	Hostname string

	// The AWS service shortcode
	Service string

	// The AWS API Action being performed
	Action string

	// Body to match against
	Body string

	// Match against specific parameters in the request.
	// This is only used for XML/Form requests (not the newer JSON ones)
	Params url.Values

	// Match a specific HTTP method
	Method string

	// Match the URL path
	Path string

	// Match the URL path, using a regex
	PathRegex *regexp.Regexp

	// Is this an instance metadata request?
	// setting this to true will match against both the IPv4 and IPv6 hostnames
	IsEc2IMDS bool

	// Matches a JSON request body by resolving the jmespath expression as keys
	// and comparing the values returned against the value provided in the map
	JMESPathMatches map[string]any

	// Write a custom matcher function that will be used to match a request.
	// this runs after checking the other fields, so you can use those as filters.
	Matcher func(*ReceivedRequest) bool

	// Stop matching this request after it has been matched X times
	//
	// 0 (default) means it will live forever
	MaxMatchCount int
	// contains filtered or unexported fields
}

Describes a request that should be matched

func (*MockedRequest) Inspect added in v0.2.0

func (m *MockedRequest) Inspect() string

Returns a string to help identify this MockedRequest

type MockedRequestHandler added in v0.2.0

type MockedRequestHandler = func(*ReceivedRequest) *http.Response

type MockedResponse

type MockedResponse struct {
	// modify the status code. default is 200
	StatusCode int

	// force the content type. default will be determined by request content type
	ContentType string

	Encoding ResponseEncoding

	// a string, struct or map that will be encoded as the response
	//
	// Also accepts a function that is of the following signatures:
	// func(*ReceivedRequest) (string) = string payload (with 200 OK, inferred content type)
	// func(*ReceivedRequest) (string, int) = string payload, <int> status code (with inferred content type)
	// func(*ReceivedRequest) (string, int, string) = string payload, <int> status code, content type
	// func(*ReceivedRequest) (*service.ACTIONOutput, error) = return the result type directly, or error
	// func(*ReceivedRequest, *service.ACTIONInput) (*service.ACTIONOutput, error) = return the result type directly, or error
	// func(*service.ACTIONInput) (*service.ACTIONOutput, error) = return the result type directly, or error
	Body any

	// Do not wrap the xml response in ACTIONResponse>ACTIONResult
	DoNotWrap bool
	RootTag   string

	// If provided, then all other fields are ignored, and the user
	// is responsible for building an HTTP response themselves
	Handler MockedRequestHandler
	// contains filtered or unexported fields
}

func MockResponse_Error added in v0.2.5

func MockResponse_Error(statusCode int, errorCode, errorMessage string) *MockedResponse

Returns an error response with a custom code and message

type MockerInfo added in v0.2.0

type MockerInfo interface {
	// URL of the proxy server
	ProxyURL() string

	// Returns a function that can be used in [http.Transport]
	Proxy() func(*http.Request) (*url.URL, error)

	// Preconfigured IMDS client
	IMDSClient() *imds.Client

	// Aws configuration to use
	Config() aws.Config
}

Returned when you start the server, provides you some information if needed

func Start added in v0.2.0

func Start(t TestingT, optFns ...MockerOptionFunc) MockerInfo

Start the mocker

type MockerOptionFunc added in v1.0.0

type MockerOptionFunc func(*mockerOptions)

func WithAWSConfigOptions added in v1.0.0

func WithAWSConfigOptions(opts ...AwsLoadOptionsFunc) MockerOptionFunc

Additional AWS LoadConfig options to pass along to the Config builder Use this if you need to add custom middleware. Don't use this to set credentials, or HTTP client, as those will be overridden

func WithEC2Metadata added in v1.0.0

func WithEC2Metadata(opts ...IMDSMockOptionFunc) MockerOptionFunc

Add mocks for the EC2 Instance Metadata Service These are not exhaustive, so if you have a special need you will have to add it.

func WithMocks added in v1.0.0

func WithMocks(mocks ...*MockedEndpoint) MockerOptionFunc

The mocks that will be responded to

func WithTimeout added in v1.0.0

func WithTimeout(value time.Duration) MockerOptionFunc

If provided, then requests that run longer than this will be terminated. Generally you should not need to set this

func WithVerbosity deprecated added in v1.0.0

func WithVerbosity(value bool) MockerOptionFunc

Add extra logging.

Deprecated: you should just use the AWSMOCKER_DEBUG=1 env var and do a targeted test run

func WithoutCredentialProtection added in v1.0.0

func WithoutCredentialProtection() MockerOptionFunc

Disables setting credential environment variables This is dangerous, because if the proxy were to fail, then your requests may actually execute on AWS with real credentials. This means if you do not properly configure the mocker, you could end up making real requests to AWS. This is not recommended. Deprecated: You should really not be using this

func WithoutDefaultMocks added in v1.0.0

func WithoutDefaultMocks() MockerOptionFunc

Default mocks for GetCallerIdentity and role assumptions will not be provided

func WithoutFailingUnhandledRequests added in v1.0.0

func WithoutFailingUnhandledRequests() MockerOptionFunc

By default, receiving an unmatched request will cause the test to be marked as failed you can pass true to this if you do not want to fail your test when the mocker receives an unmatched request

func WithoutMiddleware added in v1.0.0

func WithoutMiddleware() MockerOptionFunc

Skip installation of middleware in AWS Options Use this if you have a very specific middleware need that the mocker interferes with.

WARNING: This will severely impair the mocker if you use typed requests/responses you should probably fix the issue with using the middleware instead.

type ReceivedRequest added in v0.2.0

type ReceivedRequest struct {
	HttpRequest *http.Request

	Action  string
	Service string
	Region  string

	Hostname string
	Path     string

	// The expected response type based upon the request. JSON requests answered with JSON,
	// form param posts respond with XML
	AssumedResponseType string

	// This will only be populated if the request was NOT a form
	RawBody []byte

	// If the request was a JSON request, then this will be the parsed JSON
	JsonPayload any
	// contains filtered or unexported fields
}

func (*ReceivedRequest) DebugDump added in v0.2.0

func (r *ReceivedRequest) DebugDump()

func (*ReceivedRequest) Inspect added in v0.2.5

func (rr *ReceivedRequest) Inspect() string

type ResponseEncoding

type ResponseEncoding int
const (
	// Default will try to determine encoding via the request headers
	ResponseEncodingDefault ResponseEncoding = iota
	ResponseEncodingJSON
	ResponseEncodingXML
	ResponseEncodingText
)

type TestingT added in v0.2.0

type TestingT interface {
	Setenv(key, value string)
	TempDir() string
	Cleanup(func())
	Fail()
	Errorf(format string, args ...any)
	Logf(format string, args ...any)
}

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL