SlideShare a Scribd company logo
1 of 23
Testing Against AWS
APIs With Go
Steve Scaffidi
HERE Technologies
Github/Twitter: @hercynium
Testing live systems is
challenging
You do test, right?
Mock AWS*
*neener, neener, neener!
func main() {
awsSess, e := awsSession.NewSession()
kingpin.FatalIfError(e, "Could not create AWS API session")
instanceIds := GetAwsInstanceIds(awsSess)
for _, id := range instanceIds {
fmt.Println(id)
}
}
func GetAwsInstanceIds(awsSess *awsSession.Session) []string {
instanceIds := make([]string, 0)
ec2Api := ec2.New(awsSess)
instDescrs, err := ec2Api.DescribeInstances(&ec2.DescribeInstancesInput{})
kingpin.FatalIfError(err, "Could not get EC2 instances")
for _, res := range instDescrs.Reservations {
for _, instance := range res.Instances {
instanceIds = append(instanceIds, *instance.InstanceId)
}
}
return instanceIds
}
func main() {
awsSess, e := awsSession.NewSession()
kingpin.FatalIfError(e, "Could not create AWS API session")
instanceIds := GetAwsInstanceIds(awsSess)
for _, id := range instanceIds {
fmt.Println(id)
}
}
func GetAwsInstanceIds(awsSess *awsSession.Session) []string {
instanceIds := make([]string, 0)
ec2Api := ec2.New(awsSess)
instDescrs, err := ec2Api.DescribeInstances(&ec2.DescribeInstancesInput{})
kingpin.FatalIfError(err, "Could not get EC2 instances")
for _, res := range instDescrs.Reservations {
for _, instance := range res.Instances {
instanceIds = append(instanceIds, *instance.InstanceId)
}
}
return instanceIds
}
func main() {
awsSess, e := awsSession.NewSession()
kingpin.FatalIfError(e, "Could not create AWS API session")
instanceIds := GetAwsInstanceIds(awsSess)
for _, id := range instanceIds {
fmt.Println(id)
}
}
func GetAwsInstanceIds(awsSess *awsSession.Session) []string {
instanceIds := make([]string, 0)
ec2Api := ec2.New(awsSess)
instDescrs, err := ec2Api.DescribeInstances(&ec2.DescribeInstancesInput{})
kingpin.FatalIfError(err, "Could not get EC2 instances")
for _, res := range instDescrs.Reservations {
for _, instance := range res.Instances {
instanceIds = append(instanceIds, *instance.InstanceId)
}
}
return instanceIds
}
DescribeInstances(*ec2.DescribeInstancesInput)
(*ec2.DescribeInstancesOutput, error)
From: https://godoc.org/github.com/aws/aws-sdk-go/service/ec2/ec2iface
func main() {
awsSess, e := awsSession.NewSession()
kingpin.FatalIfError(e, "Could not create AWS API session")
instanceIds := GetAwsInstanceIds(awsSess)
for _, id := range instanceIds {
fmt.Println(id)
}
}
func GetAwsInstanceIds(awsSess *awsSession.Session) []string {
instanceIds := make([]string, 0)
ec2Api := ec2.New(awsSess)
instDescrs, err := ec2Api.DescribeInstances(&ec2.DescribeInstancesInput{})
kingpin.FatalIfError(err, "Could not get EC2 instances")
for _, res := range instDescrs.Reservations {
for _, instance := range res.Instances {
instanceIds = append(instanceIds, *instance.InstanceId)
}
}
return instanceIds
}
Mock the Client APIs
ec2.EC2 -->
ec2iface.EC2API
func main() {
awsSess, e := awsSession.NewSession()
kingpin.FatalIfError(e, "Could not create AWS API session")
ec2Api := ec2.New(awsSess)
instanceIds := GetAwsInstanceIds(ec2Api)
for _, id := range instanceIds {
fmt.Println(id)
}
}
func GetAwsInstanceIds(ec2Api ec2iface.EC2API) []string {
instanceIds := make([]string, 0)
instDescrs, err := ec2Api.DescribeInstances(&ec2.DescribeInstancesInput{})
kingpin.FatalIfError(err, "Could not get EC2 instances")
for _, res := range instDescrs.Reservations {
for _, instance := range res.Instances {
instanceIds = append(instanceIds, *instance.InstanceId)
}
}
return instanceIds
}
func main() {
awsSess, e := awsSession.NewSession()
kingpin.FatalIfError(e, "Could not create AWS API session")
ec2Api := ec2.New(awsSess)
instanceIds := GetAwsInstanceIds(ec2Api)
for _, id := range instanceIds {
fmt.Println(id)
}
}
func GetAwsInstanceIds(ec2Api ec2iface.EC2API) []string {
instanceIds := make([]string, 0)
instDescrs, err := ec2Api.DescribeInstances(&ec2.DescribeInstancesInput{})
kingpin.FatalIfError(err, "Could not get EC2 instances")
for _, res := range instDescrs.Reservations {
for _, instance := range res.Instances {
instanceIds = append(instanceIds, *instance.InstanceId)
}
}
return instanceIds
}
func main() {
awsSess, e := awsSession.NewSession()
kingpin.FatalIfError(e, "Could not create AWS API session")
ec2Api := ec2.New(awsSess)
instanceIds := GetAwsInstanceIds(ec2Api)
for _, id := range instanceIds {
fmt.Println(id)
}
}
func GetAwsInstanceIds(ec2Api ec2iface.EC2API) []string {
instanceIds := make([]string, 0)
instDescrs, err := ec2Api.DescribeInstances(&ec2.DescribeInstancesInput{})
kingpin.FatalIfError(err, "Could not get EC2 instances")
for _, res := range instDescrs.Reservations {
for _, instance := range res.Instances {
instanceIds = append(instanceIds, *instance.InstanceId)
}
}
return instanceIds
}
func makeInstance(instanceId string) *ec2.Instance {
return &ec2.Instance{InstanceId: aws.String(instanceId)}
}
func makeDescribeInstancesOutput(instanceIds []string)
*ec2.DescribeInstancesOutput {
instances := make([]*ec2.Instance, 0)
for _, id := range instanceIds {
instances = append(instances, makeInstance(id))
}
return &ec2.DescribeInstancesOutput{
Reservations: []*ec2.Reservation{
{
Instances: instances,
},
},
}
}
type testAwsEc2API struct {
ec2iface.EC2API
instanceIds []string
}
func (d testAwsEc2API) DescribeInstances(input *ec2.DescribeInstancesInput)
(*ec2.DescribeInstancesOutput, error) {
return makeDescribeInstancesOutput(d.instanceIds), nil
}
type testAwsEc2API struct {
ec2iface.EC2API
instanceIds []string
}
func (d testAwsEc2API) DescribeInstances(input *ec2.DescribeInstancesInput)
(*ec2.DescribeInstancesOutput, error) {
return makeDescribeInstancesOutput(d.instanceIds), nil
}
func Test_GetAwsInstanceIds(t *testing.T) {
tests := []struct {
name string
instanceIds []string
}{
{
name: "get all instance IDs",
instanceIds:
[]string{"i-11111", "i-22222", "i-33333"},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ec2API := &testAwsEc2API{
instanceIds: test.instanceIds,
}
gotInstanceIds := GetAwsInstanceIds(ec2API)
assert.Equal(t, test.instanceIds, gotInstanceIds)
})
}
}
func Test_GetAwsInstanceIds(t *testing.T) {
tests := []struct {
name string
instanceIds []string
}{
{
name: "get all instance IDs",
instanceIds:
[]string{"i-11111", "i-22222", "i-33333"},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ec2API := &testAwsEc2API{
instanceIds: test.instanceIds,
}
gotInstanceIds := GetAwsInstanceIds(ec2API)
assert.Equal(t, test.instanceIds, gotInstanceIds)
})
}
}
Time for a live demo?
Thank You!

More Related Content

What's hot

AST - the only true tool for building JavaScript
AST - the only true tool for building JavaScriptAST - the only true tool for building JavaScript
AST - the only true tool for building JavaScript
Ingvar Stepanyan
 

What's hot (20)

連邦の白いヤツ 「Objective-C」
連邦の白いヤツ 「Objective-C」連邦の白いヤツ 「Objective-C」
連邦の白いヤツ 「Objective-C」
 
AST - the only true tool for building JavaScript
AST - the only true tool for building JavaScriptAST - the only true tool for building JavaScript
AST - the only true tool for building JavaScript
 
Testing Javascript with Jasmine
Testing Javascript with JasmineTesting Javascript with Jasmine
Testing Javascript with Jasmine
 
First glance at Akka 2.0
First glance at Akka 2.0First glance at Akka 2.0
First glance at Akka 2.0
 
RxJS Operators - Real World Use Cases (FULL VERSION)
RxJS Operators - Real World Use Cases (FULL VERSION)RxJS Operators - Real World Use Cases (FULL VERSION)
RxJS Operators - Real World Use Cases (FULL VERSION)
 
Testing & deploying terraform
Testing & deploying terraformTesting & deploying terraform
Testing & deploying terraform
 
Refactoring Infrastructure Code
Refactoring Infrastructure CodeRefactoring Infrastructure Code
Refactoring Infrastructure Code
 
RxJS - 封裝程式的藝術
RxJS - 封裝程式的藝術RxJS - 封裝程式的藝術
RxJS - 封裝程式的藝術
 
And the Greatest of These Is ... Rack Support
And the Greatest of These Is ... Rack SupportAnd the Greatest of These Is ... Rack Support
And the Greatest of These Is ... Rack Support
 
Don't Be Afraid of Abstract Syntax Trees
Don't Be Afraid of Abstract Syntax TreesDon't Be Afraid of Abstract Syntax Trees
Don't Be Afraid of Abstract Syntax Trees
 
The battle of Protractor and Cypress - RunIT Conference 2019
The battle of Protractor and Cypress - RunIT Conference 2019The battle of Protractor and Cypress - RunIT Conference 2019
The battle of Protractor and Cypress - RunIT Conference 2019
 
Reactive programming with RxJS - ByteConf 2018
Reactive programming with RxJS - ByteConf 2018Reactive programming with RxJS - ByteConf 2018
Reactive programming with RxJS - ByteConf 2018
 
With a Mighty Hammer
With a Mighty HammerWith a Mighty Hammer
With a Mighty Hammer
 
Taking advantage of the Amazon Web Services (AWS) Family
Taking advantage of the Amazon Web Services (AWS) FamilyTaking advantage of the Amazon Web Services (AWS) Family
Taking advantage of the Amazon Web Services (AWS) Family
 
JavaScript on the GPU
JavaScript on the GPUJavaScript on the GPU
JavaScript on the GPU
 
Esprima - What is that
Esprima - What is thatEsprima - What is that
Esprima - What is that
 
Reactive computing
Reactive computingReactive computing
Reactive computing
 
Epic South Disasters
Epic South DisastersEpic South Disasters
Epic South Disasters
 
Rx.NET, from the inside out - Codemotion 2018
Rx.NET, from the inside out - Codemotion 2018Rx.NET, from the inside out - Codemotion 2018
Rx.NET, from the inside out - Codemotion 2018
 
Universal JavaScript
Universal JavaScriptUniversal JavaScript
Universal JavaScript
 

Similar to Testing Against AWS APIs Go

JSON Schema: Your API's Secret Weapon
JSON Schema: Your API's Secret WeaponJSON Schema: Your API's Secret Weapon
JSON Schema: Your API's Secret Weapon
Pete Gamache
 
Tips and tricks for building api heavy ruby on rails applications
Tips and tricks for building api heavy ruby on rails applicationsTips and tricks for building api heavy ruby on rails applications
Tips and tricks for building api heavy ruby on rails applications
Tim Cull
 
用Tornado开发RESTful API运用
用Tornado开发RESTful API运用用Tornado开发RESTful API运用
用Tornado开发RESTful API运用
Felinx Lee
 

Similar to Testing Against AWS APIs Go (20)

RESTful API using scalaz (3)
RESTful API using scalaz (3)RESTful API using scalaz (3)
RESTful API using scalaz (3)
 
Consuming Web Services with Swift and Rx
Consuming Web Services with Swift and RxConsuming Web Services with Swift and Rx
Consuming Web Services with Swift and Rx
 
Configuration Management and Provisioning Are Different
Configuration Management and Provisioning Are DifferentConfiguration Management and Provisioning Are Different
Configuration Management and Provisioning Are Different
 
GPerf Using Jesque
GPerf Using JesqueGPerf Using Jesque
GPerf Using Jesque
 
JSON Schema: Your API's Secret Weapon
JSON Schema: Your API's Secret WeaponJSON Schema: Your API's Secret Weapon
JSON Schema: Your API's Secret Weapon
 
Tips and tricks for building api heavy ruby on rails applications
Tips and tricks for building api heavy ruby on rails applicationsTips and tricks for building api heavy ruby on rails applications
Tips and tricks for building api heavy ruby on rails applications
 
ARC204 AWS Infrastructure Automation - AWS re: Invent 2012
ARC204 AWS Infrastructure Automation - AWS re: Invent 2012ARC204 AWS Infrastructure Automation - AWS re: Invent 2012
ARC204 AWS Infrastructure Automation - AWS re: Invent 2012
 
(DEV304) What’s New in the AWS SDK for .NET | AWS re:Invent 2014
(DEV304) What’s New in the AWS SDK for .NET | AWS re:Invent 2014(DEV304) What’s New in the AWS SDK for .NET | AWS re:Invent 2014
(DEV304) What’s New in the AWS SDK for .NET | AWS re:Invent 2014
 
CoffeeScript - A Rubyist's Love Affair
CoffeeScript - A Rubyist's Love AffairCoffeeScript - A Rubyist's Love Affair
CoffeeScript - A Rubyist's Love Affair
 
Expert JavaScript tricks of the masters
Expert JavaScript  tricks of the mastersExpert JavaScript  tricks of the masters
Expert JavaScript tricks of the masters
 
用Tornado开发RESTful API运用
用Tornado开发RESTful API运用用Tornado开发RESTful API运用
用Tornado开发RESTful API运用
 
Using Cerberus and PySpark to validate semi-structured datasets
Using Cerberus and PySpark to validate semi-structured datasetsUsing Cerberus and PySpark to validate semi-structured datasets
Using Cerberus and PySpark to validate semi-structured datasets
 
Stop Making Excuses and Start Testing Your JavaScript
Stop Making Excuses and Start Testing Your JavaScriptStop Making Excuses and Start Testing Your JavaScript
Stop Making Excuses and Start Testing Your JavaScript
 
java sockets
 java sockets java sockets
java sockets
 
Paws - Perl AWS SDK Update - November 2015
Paws - Perl AWS SDK Update - November 2015Paws - Perl AWS SDK Update - November 2015
Paws - Perl AWS SDK Update - November 2015
 
infrastructure as code
infrastructure as codeinfrastructure as code
infrastructure as code
 
An Introduction to Celery
An Introduction to CeleryAn Introduction to Celery
An Introduction to Celery
 
Asynchronous web apps with the Play Framework 2.0
Asynchronous web apps with the Play Framework 2.0Asynchronous web apps with the Play Framework 2.0
Asynchronous web apps with the Play Framework 2.0
 
Hidden Gems in Swift
Hidden Gems in SwiftHidden Gems in Swift
Hidden Gems in Swift
 
Test-driven Development with AEM
Test-driven Development with AEMTest-driven Development with AEM
Test-driven Development with AEM
 

Recently uploaded

Recently uploaded (20)

WSO2CON 2024 Slides - Open Source to SaaS
WSO2CON 2024 Slides - Open Source to SaaSWSO2CON 2024 Slides - Open Source to SaaS
WSO2CON 2024 Slides - Open Source to SaaS
 
WSO2Con2024 - From Blueprint to Brilliance: WSO2's Guide to API-First Enginee...
WSO2Con2024 - From Blueprint to Brilliance: WSO2's Guide to API-First Enginee...WSO2Con2024 - From Blueprint to Brilliance: WSO2's Guide to API-First Enginee...
WSO2Con2024 - From Blueprint to Brilliance: WSO2's Guide to API-First Enginee...
 
WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...
WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...
WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...
 
WSO2Con2024 - Navigating the Digital Landscape: Transforming Healthcare with ...
WSO2Con2024 - Navigating the Digital Landscape: Transforming Healthcare with ...WSO2Con2024 - Navigating the Digital Landscape: Transforming Healthcare with ...
WSO2Con2024 - Navigating the Digital Landscape: Transforming Healthcare with ...
 
WSO2CON 2024 - Unlocking the Identity: Embracing CIAM 2.0 for a Competitive A...
WSO2CON 2024 - Unlocking the Identity: Embracing CIAM 2.0 for a Competitive A...WSO2CON 2024 - Unlocking the Identity: Embracing CIAM 2.0 for a Competitive A...
WSO2CON 2024 - Unlocking the Identity: Embracing CIAM 2.0 for a Competitive A...
 
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
 
WSO2CON 2024 - How CSI Piemonte Is Apifying the Public Administration
WSO2CON 2024 - How CSI Piemonte Is Apifying the Public AdministrationWSO2CON 2024 - How CSI Piemonte Is Apifying the Public Administration
WSO2CON 2024 - How CSI Piemonte Is Apifying the Public Administration
 
WSO2Con2024 - From Code To Cloud: Fast Track Your Cloud Native Journey with C...
WSO2Con2024 - From Code To Cloud: Fast Track Your Cloud Native Journey with C...WSO2Con2024 - From Code To Cloud: Fast Track Your Cloud Native Journey with C...
WSO2Con2024 - From Code To Cloud: Fast Track Your Cloud Native Journey with C...
 
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital TransformationWSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
 
WSO2Con2024 - Simplified Integration: Unveiling the Latest Features in WSO2 L...
WSO2Con2024 - Simplified Integration: Unveiling the Latest Features in WSO2 L...WSO2Con2024 - Simplified Integration: Unveiling the Latest Features in WSO2 L...
WSO2Con2024 - Simplified Integration: Unveiling the Latest Features in WSO2 L...
 
WSO2CON 2024 - Architecting AI in the Enterprise: APIs and Applications
WSO2CON 2024 - Architecting AI in the Enterprise: APIs and ApplicationsWSO2CON 2024 - Architecting AI in the Enterprise: APIs and Applications
WSO2CON 2024 - Architecting AI in the Enterprise: APIs and Applications
 
WSO2CON 2024 - Freedom First—Unleashing Developer Potential with Open Source
WSO2CON 2024 - Freedom First—Unleashing Developer Potential with Open SourceWSO2CON 2024 - Freedom First—Unleashing Developer Potential with Open Source
WSO2CON 2024 - Freedom First—Unleashing Developer Potential with Open Source
 
WSO2CON 2024 - How to Run a Security Program
WSO2CON 2024 - How to Run a Security ProgramWSO2CON 2024 - How to Run a Security Program
WSO2CON 2024 - How to Run a Security Program
 
WSO2CON 2024 - Building a Digital Government in Uganda
WSO2CON 2024 - Building a Digital Government in UgandaWSO2CON 2024 - Building a Digital Government in Uganda
WSO2CON 2024 - Building a Digital Government in Uganda
 
WSO2CON 2024 - How CSI Piemonte Is Apifying the Public Administration
WSO2CON 2024 - How CSI Piemonte Is Apifying the Public AdministrationWSO2CON 2024 - How CSI Piemonte Is Apifying the Public Administration
WSO2CON 2024 - How CSI Piemonte Is Apifying the Public Administration
 
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
 
WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...
WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...
WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...
 
WSO2CON 2024 - WSO2's Digital Transformation Journey with Choreo: A Platforml...
WSO2CON 2024 - WSO2's Digital Transformation Journey with Choreo: A Platforml...WSO2CON 2024 - WSO2's Digital Transformation Journey with Choreo: A Platforml...
WSO2CON 2024 - WSO2's Digital Transformation Journey with Choreo: A Platforml...
 
WSO2Con2024 - Facilitating Broadband Switching Services for UK Telecoms Provi...
WSO2Con2024 - Facilitating Broadband Switching Services for UK Telecoms Provi...WSO2Con2024 - Facilitating Broadband Switching Services for UK Telecoms Provi...
WSO2Con2024 - Facilitating Broadband Switching Services for UK Telecoms Provi...
 
AI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplateAI & Machine Learning Presentation Template
AI & Machine Learning Presentation Template
 

Testing Against AWS APIs Go

  • 1. Testing Against AWS APIs With Go Steve Scaffidi HERE Technologies Github/Twitter: @hercynium
  • 2. Testing live systems is challenging
  • 3. You do test, right?
  • 5. func main() { awsSess, e := awsSession.NewSession() kingpin.FatalIfError(e, "Could not create AWS API session") instanceIds := GetAwsInstanceIds(awsSess) for _, id := range instanceIds { fmt.Println(id) } } func GetAwsInstanceIds(awsSess *awsSession.Session) []string { instanceIds := make([]string, 0) ec2Api := ec2.New(awsSess) instDescrs, err := ec2Api.DescribeInstances(&ec2.DescribeInstancesInput{}) kingpin.FatalIfError(err, "Could not get EC2 instances") for _, res := range instDescrs.Reservations { for _, instance := range res.Instances { instanceIds = append(instanceIds, *instance.InstanceId) } } return instanceIds }
  • 6. func main() { awsSess, e := awsSession.NewSession() kingpin.FatalIfError(e, "Could not create AWS API session") instanceIds := GetAwsInstanceIds(awsSess) for _, id := range instanceIds { fmt.Println(id) } } func GetAwsInstanceIds(awsSess *awsSession.Session) []string { instanceIds := make([]string, 0) ec2Api := ec2.New(awsSess) instDescrs, err := ec2Api.DescribeInstances(&ec2.DescribeInstancesInput{}) kingpin.FatalIfError(err, "Could not get EC2 instances") for _, res := range instDescrs.Reservations { for _, instance := range res.Instances { instanceIds = append(instanceIds, *instance.InstanceId) } } return instanceIds }
  • 7. func main() { awsSess, e := awsSession.NewSession() kingpin.FatalIfError(e, "Could not create AWS API session") instanceIds := GetAwsInstanceIds(awsSess) for _, id := range instanceIds { fmt.Println(id) } } func GetAwsInstanceIds(awsSess *awsSession.Session) []string { instanceIds := make([]string, 0) ec2Api := ec2.New(awsSess) instDescrs, err := ec2Api.DescribeInstances(&ec2.DescribeInstancesInput{}) kingpin.FatalIfError(err, "Could not get EC2 instances") for _, res := range instDescrs.Reservations { for _, instance := range res.Instances { instanceIds = append(instanceIds, *instance.InstanceId) } } return instanceIds }
  • 8.
  • 10.
  • 11. func main() { awsSess, e := awsSession.NewSession() kingpin.FatalIfError(e, "Could not create AWS API session") instanceIds := GetAwsInstanceIds(awsSess) for _, id := range instanceIds { fmt.Println(id) } } func GetAwsInstanceIds(awsSess *awsSession.Session) []string { instanceIds := make([]string, 0) ec2Api := ec2.New(awsSess) instDescrs, err := ec2Api.DescribeInstances(&ec2.DescribeInstancesInput{}) kingpin.FatalIfError(err, "Could not get EC2 instances") for _, res := range instDescrs.Reservations { for _, instance := range res.Instances { instanceIds = append(instanceIds, *instance.InstanceId) } } return instanceIds }
  • 14. func main() { awsSess, e := awsSession.NewSession() kingpin.FatalIfError(e, "Could not create AWS API session") ec2Api := ec2.New(awsSess) instanceIds := GetAwsInstanceIds(ec2Api) for _, id := range instanceIds { fmt.Println(id) } } func GetAwsInstanceIds(ec2Api ec2iface.EC2API) []string { instanceIds := make([]string, 0) instDescrs, err := ec2Api.DescribeInstances(&ec2.DescribeInstancesInput{}) kingpin.FatalIfError(err, "Could not get EC2 instances") for _, res := range instDescrs.Reservations { for _, instance := range res.Instances { instanceIds = append(instanceIds, *instance.InstanceId) } } return instanceIds }
  • 15. func main() { awsSess, e := awsSession.NewSession() kingpin.FatalIfError(e, "Could not create AWS API session") ec2Api := ec2.New(awsSess) instanceIds := GetAwsInstanceIds(ec2Api) for _, id := range instanceIds { fmt.Println(id) } } func GetAwsInstanceIds(ec2Api ec2iface.EC2API) []string { instanceIds := make([]string, 0) instDescrs, err := ec2Api.DescribeInstances(&ec2.DescribeInstancesInput{}) kingpin.FatalIfError(err, "Could not get EC2 instances") for _, res := range instDescrs.Reservations { for _, instance := range res.Instances { instanceIds = append(instanceIds, *instance.InstanceId) } } return instanceIds }
  • 16. func main() { awsSess, e := awsSession.NewSession() kingpin.FatalIfError(e, "Could not create AWS API session") ec2Api := ec2.New(awsSess) instanceIds := GetAwsInstanceIds(ec2Api) for _, id := range instanceIds { fmt.Println(id) } } func GetAwsInstanceIds(ec2Api ec2iface.EC2API) []string { instanceIds := make([]string, 0) instDescrs, err := ec2Api.DescribeInstances(&ec2.DescribeInstancesInput{}) kingpin.FatalIfError(err, "Could not get EC2 instances") for _, res := range instDescrs.Reservations { for _, instance := range res.Instances { instanceIds = append(instanceIds, *instance.InstanceId) } } return instanceIds }
  • 17. func makeInstance(instanceId string) *ec2.Instance { return &ec2.Instance{InstanceId: aws.String(instanceId)} } func makeDescribeInstancesOutput(instanceIds []string) *ec2.DescribeInstancesOutput { instances := make([]*ec2.Instance, 0) for _, id := range instanceIds { instances = append(instances, makeInstance(id)) } return &ec2.DescribeInstancesOutput{ Reservations: []*ec2.Reservation{ { Instances: instances, }, }, } }
  • 18. type testAwsEc2API struct { ec2iface.EC2API instanceIds []string } func (d testAwsEc2API) DescribeInstances(input *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) { return makeDescribeInstancesOutput(d.instanceIds), nil }
  • 19. type testAwsEc2API struct { ec2iface.EC2API instanceIds []string } func (d testAwsEc2API) DescribeInstances(input *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) { return makeDescribeInstancesOutput(d.instanceIds), nil }
  • 20. func Test_GetAwsInstanceIds(t *testing.T) { tests := []struct { name string instanceIds []string }{ { name: "get all instance IDs", instanceIds: []string{"i-11111", "i-22222", "i-33333"}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { ec2API := &testAwsEc2API{ instanceIds: test.instanceIds, } gotInstanceIds := GetAwsInstanceIds(ec2API) assert.Equal(t, test.instanceIds, gotInstanceIds) }) } }
  • 21. func Test_GetAwsInstanceIds(t *testing.T) { tests := []struct { name string instanceIds []string }{ { name: "get all instance IDs", instanceIds: []string{"i-11111", "i-22222", "i-33333"}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { ec2API := &testAwsEc2API{ instanceIds: test.instanceIds, } gotInstanceIds := GetAwsInstanceIds(ec2API) assert.Equal(t, test.instanceIds, gotInstanceIds) }) } }
  • 22. Time for a live demo?

Editor's Notes

  1. Good morning! This is a quick talk on how you can test AWS APIs in Go, with very little pain. My name is Steve Scaffidi, and I'm a lead engineer at HERE Technologies.
  2. When you write code that interacts with AWS, you're writing code that interacts with a "live" system. It's always changing, and testing code against live systems can be a challenge.
  3. But you *do* test it, RIGHT? Well, you can and you should.
  4. In the case of using AWS APIs, you can essentially *mock* any part of AWS, and the golang SDK's API makes it possible. So, let's look at some code that uses the AWS API.
  5. It's a simple example in this case, that asks AWS for all the available EC2 instances then returns a list of the instance-IDs.
  6. As usual, you create an AWS session...
  7. ...then instantiate the appropriate API client with that session. In this case, it's the EC2 API client.
  8. I use GoLand as my IDE and it has a handy auto-documentation function and as you can see here, the new() function returns an EC2 client object. We can then pass this around and make calls on it, to interact with the EC2 API.
  9. Now, the AWS APIs are rather consistent. You'll see a particular pattern for each method where it takes an "Input" data structure as an argument, and returns an "Output" data structure. For example, the "DescribeInstances" method on the EC2 client follows this pattern, where it takes a "DescribeInstancesInput" object and returns a "DescribeInstancesOutput" object.
  10. This is important to know, because you'll be constructing these objects for your tests later. The API documentation is rather good, and should make figuring it out straight-forward.
  11. Anyhow, we make the call, get the result, and process it to extract the info we need. Simple. But how do you test this?
  12. The special sauce? Don't mock the session, which was not made for that kind of abuse. Mock the API clients. This is vastly simpler because the client APIs were designed for it.
  13. Each API is backed by an underlying Go Interface. All you need to do is make the interface the type you pass around instead of the concrete implementation type. Once your code is refactored to do that, you can very easily write tests.
  14. Here is an example of the same code, refactored to be testable.
  15. In this case, we don't pass the AWS session around, we pass the EC2 client we instantiated with the session.
  16. But the key is that the function we're testing takes not the EC2 client object, but rather an EC2API *interface*. It's this interface that allows us to mock the EC2 client.
  17. Now, let's look at the test code... First some helper functions to construct the DescribeInstancesOutput object we want to return from our mocked client. Given a list of Instance IDs, it will create the required data structures.
  18. Now here's the mock client, with only the method we want to mock. This function simply ignores the input and constructs the output object, using the helper functions from the previous slide, but you can easily make it much more sophisticated, as my real-world code using this technique is.
  19. Note that we've augmented the mock client with an instanceIds field, which we can populate with whatever we want from our test code, thus customizing what the DescribeInstances method returns.
  20. Now, let's put it all together - here's the actual test, written in a table-driven style.
  21. The trick is highlighted here... We construct the mock client and pass that to the function we're testing, and voila, it returns exactly what we told it to return...
  22. Time for a live demo? Let me open my IDE and prove this all works, and show off the actual code!