Repository: zalando-incubator/kube-ingress-aws-controller
Pull request #587 (2023-02-28 17:10:02)

Add goldenfile tests to ingress controller
The idea of this PR is to improve the confidence of developers when making changes to the ingress controler code.

The new test will fetch k8s yamls and serve it through a fake k8s API, the ingress-controler test should generate a cloud formation template to create all the necessary AWS resources for any ingress change fetched in the fake k8s API, the generated template is then compared to an expected output detailed the golden file "expected.cf".

This PR adds the boilerplate for golden tests in the ingress-controller worker level and adds two simple cases one considering the creation of an ALB and the other considering the creation of a NLB to satisfy an ingress definition in k8s.

There is more information in the body of commit messages, please take a look. :)

lucastt: 👍 
szuecs: :+1:
lucastt: 👍 

Patch:
diff --git a/aws/adapter.go b/aws/adapter.go
index 5f6d258..7e284aa 100644
--- a/aws/adapter.go
+++ b/aws/adapter.go
@@ -248,6 +248,17 @@ func NewAdapter(clusterID, newControllerID, vpcID string, debug, disableInstrume
 	return
 }
 
+// UpdateManifest generates a manifest again based on cluster and VPC information.
+// This method is useful when using custom AWS clients.
+func (a *Adapter) UpdateManifest(clusterID, vpcID string) (*Adapter, error) {
+	m, err := buildManifest(a, clusterID, vpcID)
+	if err != nil {
+		return nil, err
+	}
+	a.manifest = m
+	return a, err
+}
+
 func (a *Adapter) NewACMCertificateProvider() certs.CertificatesProvider {
 	return newACMCertProvider(a.acm)
 }
@@ -494,6 +505,34 @@ func (a *Adapter) WithInternalDomainsDenyResponseContenType(contentType string)
 	return a
 }
 
+// WithCustomEc2Client returns an Adapter that will use the provided
+// EC2 client, instead of the EC2 client provided by AWS.
+func (a *Adapter) WithCustomEc2Client(c ec2iface.EC2API) *Adapter {
+	a.ec2 = c
+	return a
+}
+
+// WithCustomAutoScalingClient returns an Adapter that will use the provided
+// Auto scaling client, instead of the Auto scaling client provided by AWS.
+func (a *Adapter) WithCustomAutoScalingClient(c autoscalingiface.AutoScalingAPI) *Adapter {
+	a.autoscaling = c
+	return a
+}
+
+// WithCustomElbv2Client returns an Adapter that will use the provided
+// ELBv2 client, instead of the ELBv2 client provided by AWS.
+func (a *Adapter) WithCustomElbv2Client(c elbv2iface.ELBV2API) *Adapter {
+	a.elbv2 = c
+	return a
+}
+
+// WithCustomCloudFormationClient returns an Adapter that will use the provided
+// CloudFormation client, instead of the CloudFormation client provided by AWS.
+func (a *Adapter) WithCustomCloudFormationClient(c cloudformationiface.CloudFormationAPI) *Adapter {
+	a.cloudformation = c
+	return a
+}
+
 // ClusterID returns the ClusterID tag that all resources from the same Kubernetes cluster share.
 // It's taken from the current ec2 instance.
 func (a *Adapter) ClusterID() string {
diff --git a/aws/fake/api_response.go b/aws/fake/api_response.go
new file mode 100644
index 0000000..4160f13
--- /dev/null
+++ b/aws/fake/api_response.go
@@ -0,0 +1,16 @@
+package fake
+
+import "errors"
+
+type APIResponse struct {
+	response interface{}
+	err      error
+}
+
+type Tags map[string]string
+
+var ErrDummy = errors.New("fail")
+
+func R(r interface{}, e error) *APIResponse {
+	return &APIResponse{response: r, err: e}
+}
diff --git a/aws/fake/asg.go b/aws/fake/asg.go
new file mode 100644
index 0000000..1d9ce0f
--- /dev/null
+++ b/aws/fake/asg.go
@@ -0,0 +1,83 @@
+package fake
+
+import (
+	"testing"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/autoscaling"
+	"github.com/aws/aws-sdk-go/service/autoscaling/autoscalingiface"
+)
+
+type ASGtags map[string]string
+
+type ASGInputs struct {
+	AttachLoadBalancerTargetGroups func(*testing.T, *autoscaling.AttachLoadBalancerTargetGroupsInput)
+}
+
+type ASGOutputs struct {
+	DescribeAutoScalingGroups        *APIResponse
+	AttachLoadBalancerTargetGroups   *APIResponse
+	DetachLoadBalancerTargetGroups   *APIResponse
+	DescribeLoadBalancerTargetGroups *APIResponse
+}
+
+type ASGClient struct {
+	autoscalingiface.AutoScalingAPI
+	Outputs ASGOutputs
+	Inputs  ASGInputs
+	T       *testing.T
+}
+
+func (m *ASGClient) DescribeAutoScalingGroups(*autoscaling.DescribeAutoScalingGroupsInput) (*autoscaling.DescribeAutoScalingGroupsOutput, error) {
+	if out, ok := m.Outputs.DescribeAutoScalingGroups.response.(*autoscaling.DescribeAutoScalingGroupsOutput); ok {
+		return out, m.Outputs.DescribeAutoScalingGroups.err
+	}
+
+	return nil, m.Outputs.DescribeAutoScalingGroups.err
+}
+
+func (m *ASGClient) DescribeAutoScalingGroupsPages(_ *autoscaling.DescribeAutoScalingGroupsInput, fn func(*autoscaling.DescribeAutoScalingGroupsOutput, bool) bool) error {
+	if out, ok := m.Outputs.DescribeAutoScalingGroups.response.(*autoscaling.DescribeAutoScalingGroupsOutput); ok {
+		fn(out, true)
+	}
+	return m.Outputs.DescribeAutoScalingGroups.err
+}
+
+func (m *ASGClient) DescribeLoadBalancerTargetGroups(*autoscaling.DescribeLoadBalancerTargetGroupsInput) (*autoscaling.DescribeLoadBalancerTargetGroupsOutput, error) {
+	if out, ok := m.Outputs.DescribeLoadBalancerTargetGroups.response.(*autoscaling.DescribeLoadBalancerTargetGroupsOutput); ok {
+		return out, m.Outputs.DescribeLoadBalancerTargetGroups.err
+	}
+	return nil, m.Outputs.DescribeLoadBalancerTargetGroups.err
+}
+
+func (m *ASGClient) AttachLoadBalancerTargetGroups(input *autoscaling.AttachLoadBalancerTargetGroupsInput) (*autoscaling.AttachLoadBalancerTargetGroupsOutput, error) {
+	if m.Inputs.AttachLoadBalancerTargetGroups != nil {
+		m.Inputs.AttachLoadBalancerTargetGroups(m.T, input)
+	}
+	return nil, m.Outputs.AttachLoadBalancerTargetGroups.err
+}
+
+func (m *ASGClient) DetachLoadBalancerTargetGroups(*autoscaling.DetachLoadBalancerTargetGroupsInput) (*autoscaling.DetachLoadBalancerTargetGroupsOutput, error) {
+	return nil, m.Outputs.DetachLoadBalancerTargetGroups.err
+}
+
+func MockDescribeAutoScalingGroupOutput(asgs ...map[string]ASGtags) *autoscaling.DescribeAutoScalingGroupsOutput {
+	groups := make([]*autoscaling.Group, 0)
+	for _, asg := range asgs {
+		for name, tags := range asg {
+			t := make([]*autoscaling.TagDescription, 0)
+			for k, v := range tags {
+				t = append(t, &autoscaling.TagDescription{
+					Key:   aws.String(k),
+					Value: aws.String(v),
+				})
+			}
+			g := &autoscaling.Group{
+				AutoScalingGroupName: aws.String(name),
+				Tags:                 t,
+			}
+			groups = append(groups, g)
+		}
+	}
+	return &autoscaling.DescribeAutoScalingGroupsOutput{AutoScalingGroups: groups}
+}
diff --git a/aws/fake/cf.go b/aws/fake/cf.go
new file mode 100644
index 0000000..3b367f7
--- /dev/null
+++ b/aws/fake/cf.go
@@ -0,0 +1,102 @@
+package fake
+
+import (
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/cloudformation"
+	"github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface"
+)
+
+type CFOutputs struct {
+	DescribeStackPages          *APIResponse
+	DescribeStacks              *APIResponse
+	CreateStack                 *APIResponse
+	UpdateStack                 *APIResponse
+	DeleteStack                 *APIResponse
+	UpdateTerminationProtection *APIResponse
+}
+
+type CFClient struct {
+	cloudformationiface.CloudFormationAPI
+	lastGeneratedTemplate string
+	Outputs               CFOutputs
+}
+
+func (m *CFClient) GetLastGeneratedTemplate() string {
+	return m.lastGeneratedTemplate
+}
+
+func (m *CFClient) DescribeStacksPages(in *cloudformation.DescribeStacksInput, fn func(*cloudformation.DescribeStacksOutput, bool) bool) (err error) {
+	if m.Outputs.DescribeStackPages != nil {
+		err = m.Outputs.DescribeStackPages.err
+	}
+	if err != nil {
+		return
+	}
+
+	if out, ok := m.Outputs.DescribeStacks.response.(*cloudformation.DescribeStacksOutput); ok {
+		fn(out, true)
+	}
+	if m.Outputs.DescribeStacks != nil {
+		err = m.Outputs.DescribeStacks.err
+	}
+
+	return
+}
+
+func (m *CFClient) DescribeStacks(in *cloudformation.DescribeStacksInput) (*cloudformation.DescribeStacksOutput, error) {
+	out, ok := m.Outputs.DescribeStacks.response.(*cloudformation.DescribeStacksOutput)
+	if !ok {
+		return nil, m.Outputs.DescribeStacks.err
+	}
+	return out, m.Outputs.DescribeStacks.err
+}
+
+func (m *CFClient) CreateStack(params *cloudformation.CreateStackInput) (*cloudformation.CreateStackOutput, error) {
+	m.lastGeneratedTemplate = *params.TemplateBody
+	out, ok := m.Outputs.CreateStack.response.(*cloudformation.CreateStackOutput)
+	if !ok {
+		return nil, m.Outputs.CreateStack.err
+	}
+	return out, m.Outputs.CreateStack.err
+}
+
+func MockCSOutput(stackId string) *cloudformation.CreateStackOutput {
+	return &cloudformation.CreateStackOutput{
+		StackId: aws.String(stackId),
+	}
+}
+
+func (m *CFClient) UpdateStack(params *cloudformation.UpdateStackInput) (*cloudformation.UpdateStackOutput, error) {
+	m.lastGeneratedTemplate = *params.TemplateBody
+	out, ok := m.Outputs.UpdateStack.response.(*cloudformation.UpdateStackOutput)
+	if !ok {
+		return nil, m.Outputs.UpdateStack.err
+	}
+	return out, m.Outputs.UpdateStack.err
+}
+
+func MockUSOutput(stackId string) *cloudformation.UpdateStackOutput {
+	return &cloudformation.UpdateStackOutput{
+		StackId: aws.String(stackId),
+	}
+}
+
+func (m *CFClient) DeleteStack(params *cloudformation.DeleteStackInput) (*cloudformation.DeleteStackOutput, error) {
+	out, ok := m.Outputs.DeleteStack.response.(*cloudformation.DeleteStackOutput)
+	if !ok {
+		return nil, m.Outputs.DeleteStack.err
+	}
+	return out, m.Outputs.DeleteStack.err
+}
+
+func MockDeleteStackOutput(stackId string) *cloudformation.DeleteStackOutput {
+	return &cloudformation.DeleteStackOutput{}
+}
+
+func (m *CFClient) UpdateTerminationProtection(params *cloudformation.UpdateTerminationProtectionInput) (*cloudformation.UpdateTerminationProtectionOutput, error) {
+	out, ok := m.Outputs.UpdateTerminationProtection.response.(*cloudformation.UpdateTerminationProtectionOutput)
+	if !ok {
+		return nil, m.Outputs.UpdateTerminationProtection.err
+	}
+	return out, m.Outputs.UpdateTerminationProtection.err
+}
diff --git a/aws/fake/ec2.go b/aws/fake/ec2.go
new file mode 100644
index 0000000..2dad31b
--- /dev/null
+++ b/aws/fake/ec2.go
@@ -0,0 +1,166 @@
+package fake
+
+import (
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/ec2"
+	"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
+)
+
+const dipSplitSize = 2
+
+type EC2Outputs struct {
+	DescribeSecurityGroups *APIResponse
+	DescribeInstances      *APIResponse
+	DescribeInstancesPages []*APIResponse
+	DescribeSubnets        *APIResponse
+	DescribeRouteTables    *APIResponse
+}
+
+type EC2Client struct {
+	ec2iface.EC2API
+	Outputs EC2Outputs
+}
+
+func (m *EC2Client) DescribeSecurityGroups(*ec2.DescribeSecurityGroupsInput) (*ec2.DescribeSecurityGroupsOutput, error) {
+	out, ok := m.Outputs.DescribeSecurityGroups.response.(*ec2.DescribeSecurityGroupsOutput)
+	if !ok {
+		return nil, m.Outputs.DescribeSecurityGroups.err
+	}
+	return out, m.Outputs.DescribeSecurityGroups.err
+}
+
+func (m *EC2Client) DescribeInstances(*ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) {
+	out, ok := m.Outputs.DescribeInstances.response.(*ec2.DescribeInstancesOutput)
+	if !ok {
+		return nil, m.Outputs.DescribeInstances.err
+	}
+	return out, m.Outputs.DescribeInstances.err
+}
+
+func (m *EC2Client) DescribeInstancesPages(params *ec2.DescribeInstancesInput, f func(*ec2.DescribeInstancesOutput, bool) bool) error {
+	for _, resp := range m.Outputs.DescribeInstancesPages {
+		if out, ok := resp.response.(*ec2.DescribeInstancesOutput); ok {
+			f(out, true)
+		}
+	}
+	if len(m.Outputs.DescribeInstancesPages) != 0 {
+		return m.Outputs.DescribeInstancesPages[0].err
+	}
+	return nil
+}
+
+func (m *EC2Client) DescribeSubnets(*ec2.DescribeSubnetsInput) (*ec2.DescribeSubnetsOutput, error) {
+	out, ok := m.Outputs.DescribeSubnets.response.(*ec2.DescribeSubnetsOutput)
+	if !ok {
+		return nil, m.Outputs.DescribeSubnets.err
+	}
+	return out, m.Outputs.DescribeSubnets.err
+}
+
+func (m *EC2Client) DescribeRouteTables(*ec2.DescribeRouteTablesInput) (*ec2.DescribeRouteTablesOutput, error) {
+	out, ok := m.Outputs.DescribeRouteTables.response.(*ec2.DescribeRouteTablesOutput)
+	if !ok {
+		return nil, m.Outputs.DescribeRouteTables.err
+	}
+	return out, m.Outputs.DescribeRouteTables.err
+}
+
+func MockDescribeSecurityGroupsOutput(sgs map[string]string) *ec2.DescribeSecurityGroupsOutput {
+	groups := make([]*ec2.SecurityGroup, 0)
+	for id, name := range sgs {
+		sg := &ec2.SecurityGroup{
+			GroupId:   aws.String(id),
+			GroupName: aws.String(name),
+		}
+		groups = append(groups, sg)
+	}
+	return &ec2.DescribeSecurityGroupsOutput{SecurityGroups: groups}
+}
+
+type TestInstance struct {
+	Id        string
+	Tags      Tags
+	PrivateIp string
+	VpcId     string
+	State     int64
+}
+
+func MockDescribeInstancesOutput(mockedInstances ...TestInstance) *ec2.DescribeInstancesOutput {
+	instances := make([]*ec2.Instance, 0, len(mockedInstances))
+	for _, i := range mockedInstances {
+		tags := make([]*ec2.Tag, 0, len(i.Tags))
+		for k, v := range i.Tags {
+			tags = append(tags, &ec2.Tag{Key: aws.String(k), Value: aws.String(v)})
+		}
+		instance := &ec2.Instance{
+			InstanceId:       aws.String(i.Id),
+			Tags:             tags,
+			State:            &ec2.InstanceState{Code: aws.Int64(i.State)},
+			PrivateIpAddress: aws.String(i.PrivateIp),
+			VpcId:            aws.String(i.VpcId),
+		}
+		instances = append(instances, instance)
+	}
+	return &ec2.DescribeInstancesOutput{Reservations: []*ec2.Reservation{{Instances: instances}}}
+}
+
+func MockDescribeInstancesPagesOutput(e error, mockedInstances ...TestInstance) []*APIResponse {
+	pages := len(mockedInstances) / dipSplitSize
+	result := make([]*APIResponse, pages, pages+1)
+	for i := 0; i < pages; i++ {
+		result[i] = R(MockDescribeInstancesOutput(mockedInstances[i*dipSplitSize:(i+1)*dipSplitSize]...), e)
+	}
+	if len(mockedInstances)%dipSplitSize != 0 {
+		result = append(result, R(MockDescribeInstancesOutput(mockedInstances[pages*dipSplitSize:]...), e))
+	}
+	return result
+}
+
+type TestSubnet struct {
+	Id   string
+	Az   string
+	Name string
+	Tags map[string]string
+}
+
+func MockDescribeSubnetsOutput(mockedSubnets ...TestSubnet) *ec2.DescribeSubnetsOutput {
+	subnets := make([]*ec2.Subnet, 0, len(mockedSubnets))
+	for _, subnet := range mockedSubnets {
+		s := &ec2.Subnet{
+			SubnetId:         aws.String(subnet.Id),
+			AvailabilityZone: aws.String(subnet.Az),
+			Tags: []*ec2.Tag{
+				{Key: aws.String("Name"), Value: aws.String(subnet.Name)},
+			},
+		}
+		for k, v := range subnet.Tags {
+			s.Tags = append(s.Tags, &ec2.Tag{Key: aws.String(k), Value: aws.String(v)})
+		}
+		subnets = append(subnets, s)
+	}
+	return &ec2.DescribeSubnetsOutput{Subnets: subnets}
+}
+
+type TestRouteTable struct {
+	SubnetID   string
+	Main       bool
+	GatewayIds []string
+}
+
+func MockDescribeRouteTableOutput(mockedRouteTables ...TestRouteTable) *ec2.DescribeRouteTablesOutput {
+	routeTables := make([]*ec2.RouteTable, 0, len(mockedRouteTables))
+	for _, mrt := range mockedRouteTables {
+		routes := make([]*ec2.Route, 0, len(mrt.GatewayIds))
+		for _, gwID := range mrt.GatewayIds {
+			routes = append(routes, &ec2.Route{GatewayId: aws.String(gwID)})
+		}
+		rt := &ec2.RouteTable{
+			Associations: []*ec2.RouteTableAssociation{
+				{SubnetId: aws.String(mrt.SubnetID), Main: aws.Bool(mrt.Main)},
+			},
+			Routes: routes,
+		}
+		routeTables = append(routeTables, rt)
+	}
+	return &ec2.DescribeRouteTablesOutput{RouteTables: routeTables}
+}
diff --git a/aws/fake/elbv2.go b/aws/fake/elbv2.go
new file mode 100644
index 0000000..3bc42b2
--- /dev/null
+++ b/aws/fake/elbv2.go
@@ -0,0 +1,72 @@
+package fake
+
+import (
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/request"
+	"github.com/aws/aws-sdk-go/service/elbv2"
+	"github.com/aws/aws-sdk-go/service/elbv2/elbv2iface"
+)
+
+type ELBv2Outputs struct {
+	RegisterTargets      *APIResponse
+	DeregisterTargets    *APIResponse
+	DescribeTags         *APIResponse
+	DescribeTargetGroups *APIResponse
+	DescribeTargetHealth *APIResponse
+}
+
+type ELBv2Client struct {
+	elbv2iface.ELBV2API
+	Outputs  ELBv2Outputs
+	Rtinputs []*elbv2.RegisterTargetsInput
+	Dtinputs []*elbv2.DeregisterTargetsInput
+}
+
+func (m *ELBv2Client) RegisterTargets(in *elbv2.RegisterTargetsInput) (*elbv2.RegisterTargetsOutput, error) {
+	m.Rtinputs = append(m.Rtinputs, in)
+	out, ok := m.Outputs.RegisterTargets.response.(*elbv2.RegisterTargetsOutput)
+	if !ok {
+		return nil, m.Outputs.RegisterTargets.err
+	}
+	return out, m.Outputs.RegisterTargets.err
+}
+
+func MockRTOutput() *elbv2.RegisterTargetsOutput {
+	return &elbv2.RegisterTargetsOutput{}
+}
+
+func (m *ELBv2Client) DeregisterTargets(in *elbv2.DeregisterTargetsInput) (*elbv2.DeregisterTargetsOutput, error) {
+	m.Dtinputs = append(m.Dtinputs, in)
+	out, ok := m.Outputs.DeregisterTargets.response.(*elbv2.DeregisterTargetsOutput)
+	if !ok {
+		return nil, m.Outputs.DeregisterTargets.err
+	}
+	return out, m.Outputs.DeregisterTargets.err
+}
+
+func (m *ELBv2Client) DescribeTags(tags *elbv2.DescribeTagsInput) (*elbv2.DescribeTagsOutput, error) {
+	out, ok := m.Outputs.DescribeTags.response.(*elbv2.DescribeTagsOutput)
+	if !ok {
+		return nil, m.Outputs.DescribeTags.err
+	}
+	return out, m.Outputs.DescribeTags.err
+}
+
+func (m *ELBv2Client) DescribeTargetGroupsPagesWithContext(ctx aws.Context, in *elbv2.DescribeTargetGroupsInput, f func(resp *elbv2.DescribeTargetGroupsOutput, lastPage bool) bool, opt ...request.Option) error {
+	if out, ok := m.Outputs.DescribeTargetGroups.response.(*elbv2.DescribeTargetGroupsOutput); ok {
+		f(out, true)
+	}
+	return m.Outputs.DescribeTargetGroups.err
+}
+
+func (m *ELBv2Client) DescribeTargetHealth(*elbv2.DescribeTargetHealthInput) (*elbv2.DescribeTargetHealthOutput, error) {
+	out, ok := m.Outputs.DescribeTargetHealth.response.(*elbv2.DescribeTargetHealthOutput)
+	if !ok {
+		return nil, m.Outputs.DescribeTargetHealth.err
+	}
+	return out, m.Outputs.DescribeTargetHealth.err
+}
+
+func MockDeregisterTargetsOutput() *elbv2.DeregisterTargetsOutput {
+	return &elbv2.DeregisterTargetsOutput{}
+}
diff --git a/certs/provider.go b/certs/provider.go
index 015225c..b73be76 100644
--- a/certs/provider.go
+++ b/certs/provider.go
@@ -16,6 +16,7 @@ type CertificateSummary struct {
 	id          string
 	certificate *x509.Certificate
 	chain       *x509.CertPool
+	roots       *x509.CertPool
 	domainNames []string
 }
 
@@ -42,6 +43,13 @@ func NewCertificate(id string, certificate *x509.Certificate, chain []*x509.Cert
 	}
 }
 
+// WithRoots enables you to override the root certificate pool.
+// This should be only used for test purposes.
+func (c *CertificateSummary) WithRoots(r *x509.CertPool) *CertificateSummary {
+	c.roots = r
+	return c
+}
+
 // ID returns the certificate ID for the underlying provider
 func (c *CertificateSummary) ID() string {
 	return c.id
@@ -70,7 +78,7 @@ func (c *CertificateSummary) Verify(hostname string) error {
 	opts := x509.VerifyOptions{
 		DNSName:       hostname,
 		KeyUsages:     []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
-		Roots:         roots,
+		Roots:         c.roots,
 		Intermediates: c.chain,
 		CurrentTime:   currentTime(),
 	}
@@ -80,6 +88,3 @@ func (c *CertificateSummary) Verify(hostname string) error {
 
 // For tests: allow overriding current time
 var currentTime = time.Now
-
-// For tests: allow overriding root certificate pool
-var roots *x509.CertPool
diff --git a/go.mod b/go.mod
index 3598606..86bacb4 100644
--- a/go.mod
+++ b/go.mod
@@ -32,6 +32,7 @@ require (
 	github.com/dgryski/go-jump v0.0.0-20211018200510-ba001c3ffce0 // indirect
 	github.com/dgryski/go-mpchash v0.0.0-20200819201138-7382f34c4cd1 // indirect
 	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
+	github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 // indirect
 	github.com/evanphx/json-patch v4.11.0+incompatible // indirect
 	github.com/go-logr/logr v1.2.2 // indirect
 	github.com/go-playground/locales v0.14.1 // indirect
diff --git a/go.sum b/go.sum
index 0d03955..8366094 100644
--- a/go.sum
+++ b/go.sum
@@ -85,6 +85,7 @@ github.com/dgryski/go-mpchash v0.0.0-20200819201138-7382f34c4cd1/go.mod h1:ut6ck
 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
 github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 h1:MGKhKyiYrvMDZsmLR/+RGffQSXwEkXgfLSA08qDn9AI=
+github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598/go.mod h1:0FpDmbrt36utu8jEmeU05dPC9AB5tsLYVVi+ZHfyuwI=
 github.com/docker/distribution v2.8.0+incompatible h1:l9EaZDICImO1ngI+uTifW+ZYvvz7fKISBAKpg+MbWbY=
 github.com/docker/docker v20.10.11+incompatible h1:OqzI/g/W54LczvhnccGqniFoQghHx3pklbLuhfXpqGo=
 github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
diff --git a/worker.go b/worker.go
index ddba79e..29a8d10 100644
--- a/worker.go
+++ b/worker.go
@@ -270,7 +270,6 @@ func doWork(
 	globalWAFACL string,
 ) (problems *problem.List) {
 	problems = new(problem.List)
-
 	defer func() {
 		if r := recover(); r != nil {
 			debug.PrintStack()


Test patch:
diff --git a/aws/adapter_test.go b/aws/adapter_test.go
index 5cff74f..9a06ae6 100644
--- a/aws/adapter_test.go
+++ b/aws/adapter_test.go
@@ -12,6 +12,8 @@ import (
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+
+	"github.com/zalando-incubator/kube-ingress-aws-controller/aws/fake"
 )
 
 func TesGenerateDefaultFilters(tt *testing.T) {
@@ -259,8 +261,8 @@ func TestUpdateAutoScalingGroupsAndInstances(tt *testing.T) {
 	}
 	for _, test := range []struct {
 		name                       string
-		ec2responses               ec2MockOutputs
-		asgresponses               autoscalingMockOutputs
+		ec2responses               fake.EC2Outputs
+		asgresponses               fake.ASGOutputs
 		cacheSize                  int
 		wantAsgs                   []string
 		wantSingleInstances        []string
@@ -270,13 +272,13 @@ func TestUpdateAutoScalingGroupsAndInstances(tt *testing.T) {
 	}{
 		{
 			"initial",
-			ec2MockOutputs{describeInstancesPages: mockDIPOutput(
+			fake.EC2Outputs{DescribeInstancesPages: fake.MockDescribeInstancesPagesOutput(
 				nil,
-				testInstance{id: "foo0", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.3", vpcId: "1", state: runningState},
-				testInstance{id: "foo1", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.4", vpcId: "1", state: runningState},
+				fake.TestInstance{Id: "foo0", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.3", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "foo1", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.4", VpcId: "1", State: runningState},
 			)},
-			autoscalingMockOutputs{
-				describeAutoScalingGroups: R(mockDASGOutput(map[string]asgtags{"asg1": {
+			fake.ASGOutputs{
+				DescribeAutoScalingGroups: fake.R(fake.MockDescribeAutoScalingGroupOutput(map[string]fake.ASGtags{"asg1": {
 					"foo":                          "bar",
 					clusterIDTagPrefix + clusterID: resourceLifecycleOwned,
 				}}), nil),
@@ -290,14 +292,14 @@ func TestUpdateAutoScalingGroupsAndInstances(tt *testing.T) {
 		},
 		{
 			"add-node-same-asg",
-			ec2MockOutputs{describeInstancesPages: mockDIPOutput(
+			fake.EC2Outputs{DescribeInstancesPages: fake.MockDescribeInstancesPagesOutput(
 				nil,
-				testInstance{id: "foo0", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.3", vpcId: "1", state: runningState},
-				testInstance{id: "foo1", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.4", vpcId: "1", state: 0},
-				testInstance{id: "foo2", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.5", vpcId: "1", state: runningState},
+				fake.TestInstance{Id: "foo0", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.3", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "foo1", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.4", VpcId: "1", State: 0},
+				fake.TestInstance{Id: "foo2", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.5", VpcId: "1", State: runningState},
 			)},
-			autoscalingMockOutputs{
-				describeAutoScalingGroups: R(mockDASGOutput(map[string]asgtags{"asg1": {
+			fake.ASGOutputs{
+				DescribeAutoScalingGroups: fake.R(fake.MockDescribeAutoScalingGroupOutput(map[string]fake.ASGtags{"asg1": {
 					"foo":                          "bar",
 					clusterIDTagPrefix + clusterID: resourceLifecycleOwned,
 				}}), nil),
@@ -311,15 +313,15 @@ func TestUpdateAutoScalingGroupsAndInstances(tt *testing.T) {
 		},
 		{
 			"add-node-second-asg",
-			ec2MockOutputs{describeInstancesPages: mockDIPOutput(
+			fake.EC2Outputs{DescribeInstancesPages: fake.MockDescribeInstancesPagesOutput(
 				nil,
-				testInstance{id: "foo0", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.3", vpcId: "1", state: runningState},
-				testInstance{id: "foo1", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.4", vpcId: "1", state: runningState},
-				testInstance{id: "foo2", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.5", vpcId: "1", state: runningState},
-				testInstance{id: "bar1", tags: tags{"aws:autoscaling:groupName": "asg2"}, privateIp: "2.1.1.1", vpcId: "1", state: runningState},
+				fake.TestInstance{Id: "foo0", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.3", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "foo1", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.4", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "foo2", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.5", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "bar1", Tags: fake.Tags{"aws:autoscaling:groupName": "asg2"}, PrivateIp: "2.1.1.1", VpcId: "1", State: runningState},
 			)},
-			autoscalingMockOutputs{
-				describeAutoScalingGroups: R(mockDASGOutput(map[string]asgtags{
+			fake.ASGOutputs{
+				DescribeAutoScalingGroups: fake.R(fake.MockDescribeAutoScalingGroupOutput(map[string]fake.ASGtags{
 					"asg1": {
 						"foo":                          "bar",
 						clusterIDTagPrefix + clusterID: resourceLifecycleOwned,
@@ -338,16 +340,16 @@ func TestUpdateAutoScalingGroupsAndInstances(tt *testing.T) {
 		},
 		{
 			"add-another-node-second-asg",
-			ec2MockOutputs{describeInstancesPages: mockDIPOutput(
+			fake.EC2Outputs{DescribeInstancesPages: fake.MockDescribeInstancesPagesOutput(
 				nil,
-				testInstance{id: "foo0", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.3", vpcId: "1", state: runningState},
-				testInstance{id: "foo1", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.4", vpcId: "1", state: runningState},
-				testInstance{id: "foo2", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.5", vpcId: "1", state: runningState},
-				testInstance{id: "bar1", tags: tags{"aws:autoscaling:groupName": "asg2"}, privateIp: "2.1.1.1", vpcId: "1", state: runningState},
-				testInstance{id: "bar2", tags: tags{"aws:autoscaling:groupName": "asg2"}, privateIp: "2.2.2.2", vpcId: "1", state: runningState},
+				fake.TestInstance{Id: "foo0", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.3", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "foo1", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.4", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "foo2", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.5", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "bar1", Tags: fake.Tags{"aws:autoscaling:groupName": "asg2"}, PrivateIp: "2.1.1.1", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "bar2", Tags: fake.Tags{"aws:autoscaling:groupName": "asg2"}, PrivateIp: "2.2.2.2", VpcId: "1", State: runningState},
 			)},
-			autoscalingMockOutputs{
-				describeAutoScalingGroups: R(mockDASGOutput(map[string]asgtags{
+			fake.ASGOutputs{
+				DescribeAutoScalingGroups: fake.R(fake.MockDescribeAutoScalingGroupOutput(map[string]fake.ASGtags{
 					"asg1": {
 						"foo":                          "bar",
 						clusterIDTagPrefix + clusterID: resourceLifecycleOwned,
@@ -366,17 +368,17 @@ func TestUpdateAutoScalingGroupsAndInstances(tt *testing.T) {
 		},
 		{
 			"add-node-third-asg",
-			ec2MockOutputs{describeInstancesPages: mockDIPOutput(
+			fake.EC2Outputs{DescribeInstancesPages: fake.MockDescribeInstancesPagesOutput(
 				nil,
-				testInstance{id: "foo0", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.3", vpcId: "1", state: runningState},
-				testInstance{id: "foo1", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.4", vpcId: "1", state: runningState},
-				testInstance{id: "foo2", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.5", vpcId: "1", state: runningState},
-				testInstance{id: "bar1", tags: tags{"aws:autoscaling:groupName": "asg2"}, privateIp: "2.1.1.1", vpcId: "1", state: runningState},
-				testInstance{id: "bar2", tags: tags{"aws:autoscaling:groupName": "asg2"}, privateIp: "2.2.2.2", vpcId: "1", state: runningState},
-				testInstance{id: "baz1", tags: tags{"aws:autoscaling:groupName": "asg3"}, privateIp: "3.1.1.1", vpcId: "1", state: runningState},
+				fake.TestInstance{Id: "foo0", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.3", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "foo1", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.4", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "foo2", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.5", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "bar1", Tags: fake.Tags{"aws:autoscaling:groupName": "asg2"}, PrivateIp: "2.1.1.1", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "bar2", Tags: fake.Tags{"aws:autoscaling:groupName": "asg2"}, PrivateIp: "2.2.2.2", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "baz1", Tags: fake.Tags{"aws:autoscaling:groupName": "asg3"}, PrivateIp: "3.1.1.1", VpcId: "1", State: runningState},
 			)},
-			autoscalingMockOutputs{
-				describeAutoScalingGroups: R(mockDASGOutput(map[string]asgtags{
+			fake.ASGOutputs{
+				DescribeAutoScalingGroups: fake.R(fake.MockDescribeAutoScalingGroupOutput(map[string]fake.ASGtags{
 					"asg1": {
 						"foo":                          "bar",
 						clusterIDTagPrefix + clusterID: resourceLifecycleOwned,
@@ -399,18 +401,18 @@ func TestUpdateAutoScalingGroupsAndInstances(tt *testing.T) {
 		},
 		{
 			"add-node-without-asg",
-			ec2MockOutputs{describeInstancesPages: mockDIPOutput(
+			fake.EC2Outputs{DescribeInstancesPages: fake.MockDescribeInstancesPagesOutput(
 				nil,
-				testInstance{id: "foo0", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.3", vpcId: "1", state: runningState},
-				testInstance{id: "foo1", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.4", vpcId: "1", state: runningState},
-				testInstance{id: "foo2", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.5", vpcId: "1", state: runningState},
-				testInstance{id: "bar1", tags: tags{"aws:autoscaling:groupName": "asg2"}, privateIp: "2.1.1.1", vpcId: "1", state: runningState},
-				testInstance{id: "bar2", tags: tags{"aws:autoscaling:groupName": "asg2"}, privateIp: "2.2.2.2", vpcId: "1", state: runningState},
-				testInstance{id: "baz1", tags: tags{"aws:autoscaling:groupName": "asg3"}, privateIp: "3.1.1.1", vpcId: "1", state: runningState},
-				testInstance{id: "sgl1", tags: tags{"Name": "node1"}, privateIp: "0.1.1.1", vpcId: "1", state: runningState},
+				fake.TestInstance{Id: "foo0", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.3", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "foo1", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.4", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "foo2", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.5", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "bar1", Tags: fake.Tags{"aws:autoscaling:groupName": "asg2"}, PrivateIp: "2.1.1.1", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "bar2", Tags: fake.Tags{"aws:autoscaling:groupName": "asg2"}, PrivateIp: "2.2.2.2", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "baz1", Tags: fake.Tags{"aws:autoscaling:groupName": "asg3"}, PrivateIp: "3.1.1.1", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "sgl1", Tags: fake.Tags{"Name": "node1"}, PrivateIp: "0.1.1.1", VpcId: "1", State: runningState},
 			)},
-			autoscalingMockOutputs{
-				describeAutoScalingGroups: R(mockDASGOutput(map[string]asgtags{
+			fake.ASGOutputs{
+				DescribeAutoScalingGroups: fake.R(fake.MockDescribeAutoScalingGroupOutput(map[string]fake.ASGtags{
 					"asg1": {
 						"foo":                          "bar",
 						clusterIDTagPrefix + clusterID: resourceLifecycleOwned,
@@ -433,19 +435,19 @@ func TestUpdateAutoScalingGroupsAndInstances(tt *testing.T) {
 		},
 		{
 			"add-stopped-node-without-asg",
-			ec2MockOutputs{describeInstancesPages: mockDIPOutput(
+			fake.EC2Outputs{DescribeInstancesPages: fake.MockDescribeInstancesPagesOutput(
 				nil,
-				testInstance{id: "foo0", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.3", vpcId: "1", state: runningState},
-				testInstance{id: "foo1", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.4", vpcId: "1", state: runningState},
-				testInstance{id: "foo2", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.5", vpcId: "1", state: runningState},
-				testInstance{id: "bar1", tags: tags{"aws:autoscaling:groupName": "asg2"}, privateIp: "2.1.1.1", vpcId: "1", state: runningState},
-				testInstance{id: "bar2", tags: tags{"aws:autoscaling:groupName": "asg2"}, privateIp: "2.2.2.2", vpcId: "1", state: runningState},
-				testInstance{id: "baz1", tags: tags{"aws:autoscaling:groupName": "asg3"}, privateIp: "3.1.1.1", vpcId: "1", state: runningState},
-				testInstance{id: "sgl1", tags: tags{"Name": "node1"}, privateIp: "0.1.1.1", vpcId: "1", state: runningState},
-				testInstance{id: "sgl2", tags: tags{"Name": "node2"}, privateIp: "0.1.1.2", vpcId: "1", state: stoppedState},
+				fake.TestInstance{Id: "foo0", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.3", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "foo1", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.4", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "foo2", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.5", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "bar1", Tags: fake.Tags{"aws:autoscaling:groupName": "asg2"}, PrivateIp: "2.1.1.1", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "bar2", Tags: fake.Tags{"aws:autoscaling:groupName": "asg2"}, PrivateIp: "2.2.2.2", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "baz1", Tags: fake.Tags{"aws:autoscaling:groupName": "asg3"}, PrivateIp: "3.1.1.1", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "sgl1", Tags: fake.Tags{"Name": "node1"}, PrivateIp: "0.1.1.1", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "sgl2", Tags: fake.Tags{"Name": "node2"}, PrivateIp: "0.1.1.2", VpcId: "1", State: stoppedState},
 			)},
-			autoscalingMockOutputs{
-				describeAutoScalingGroups: R(mockDASGOutput(map[string]asgtags{
+			fake.ASGOutputs{
+				DescribeAutoScalingGroups: fake.R(fake.MockDescribeAutoScalingGroupOutput(map[string]fake.ASGtags{
 					"asg1": {
 						"foo":                          "bar",
 						clusterIDTagPrefix + clusterID: resourceLifecycleOwned,
@@ -468,17 +470,17 @@ func TestUpdateAutoScalingGroupsAndInstances(tt *testing.T) {
 		},
 		{
 			"remove-third-asg-node-and-stopped-instance",
-			ec2MockOutputs{describeInstancesPages: mockDIPOutput(
+			fake.EC2Outputs{DescribeInstancesPages: fake.MockDescribeInstancesPagesOutput(
 				nil,
-				testInstance{id: "foo0", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.3", vpcId: "1", state: runningState},
-				testInstance{id: "foo1", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.4", vpcId: "1", state: runningState},
-				testInstance{id: "foo2", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.5", vpcId: "1", state: runningState},
-				testInstance{id: "bar1", tags: tags{"aws:autoscaling:groupName": "asg2"}, privateIp: "2.1.1.1", vpcId: "1", state: runningState},
-				testInstance{id: "bar2", tags: tags{"aws:autoscaling:groupName": "asg2"}, privateIp: "2.2.2.2", vpcId: "1", state: runningState},
-				testInstance{id: "sgl1", tags: tags{"Name": "node1"}, privateIp: "0.1.1.1", vpcId: "1", state: runningState},
+				fake.TestInstance{Id: "foo0", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.3", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "foo1", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.4", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "foo2", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.5", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "bar1", Tags: fake.Tags{"aws:autoscaling:groupName": "asg2"}, PrivateIp: "2.1.1.1", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "bar2", Tags: fake.Tags{"aws:autoscaling:groupName": "asg2"}, PrivateIp: "2.2.2.2", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "sgl1", Tags: fake.Tags{"Name": "node1"}, PrivateIp: "0.1.1.1", VpcId: "1", State: runningState},
 			)},
-			autoscalingMockOutputs{
-				describeAutoScalingGroups: R(mockDASGOutput(map[string]asgtags{
+			fake.ASGOutputs{
+				DescribeAutoScalingGroups: fake.R(fake.MockDescribeAutoScalingGroupOutput(map[string]fake.ASGtags{
 					"asg1": {
 						"foo":                          "bar",
 						clusterIDTagPrefix + clusterID: resourceLifecycleOwned,
@@ -497,11 +499,11 @@ func TestUpdateAutoScalingGroupsAndInstances(tt *testing.T) {
 		},
 		{
 			"error-fetching-instance",
-			ec2MockOutputs{describeInstancesPages: mockDIPOutput(
-				errDummy,
-				testInstance{},
+			fake.EC2Outputs{DescribeInstancesPages: fake.MockDescribeInstancesPagesOutput(
+				fake.ErrDummy,
+				fake.TestInstance{},
 			)},
-			autoscalingMockOutputs{},
+			fake.ASGOutputs{},
 			6,
 			[]string{"asg1", "asg2"},
 			[]string{"sgl1"},
@@ -511,18 +513,18 @@ func TestUpdateAutoScalingGroupsAndInstances(tt *testing.T) {
 		},
 		{
 			"error-fetching-asg",
-			ec2MockOutputs{describeInstancesPages: mockDIPOutput(
+			fake.EC2Outputs{DescribeInstancesPages: fake.MockDescribeInstancesPagesOutput(
 				nil,
-				testInstance{id: "foo0", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.3", vpcId: "1", state: runningState},
-				testInstance{id: "foo1", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.4", vpcId: "1", state: runningState},
-				testInstance{id: "foo2", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.5", vpcId: "1", state: runningState},
-				testInstance{id: "bar1", tags: tags{"aws:autoscaling:groupName": "asg2"}, privateIp: "2.1.1.1", vpcId: "1", state: runningState},
-				testInstance{id: "bar2", tags: tags{"aws:autoscaling:groupName": "asg2"}, privateIp: "2.2.2.2", vpcId: "1", state: runningState},
-				testInstance{id: "sgl1", tags: tags{"Name": "node1"}, privateIp: "0.1.1.1", vpcId: "1", state: runningState},
-				testInstance{id: "fail", tags: tags{"aws:autoscaling:groupName": "none"}, privateIp: "0.2.2.2", vpcId: "1", state: runningState},
+				fake.TestInstance{Id: "foo0", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.3", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "foo1", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.4", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "foo2", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.5", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "bar1", Tags: fake.Tags{"aws:autoscaling:groupName": "asg2"}, PrivateIp: "2.1.1.1", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "bar2", Tags: fake.Tags{"aws:autoscaling:groupName": "asg2"}, PrivateIp: "2.2.2.2", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "sgl1", Tags: fake.Tags{"Name": "node1"}, PrivateIp: "0.1.1.1", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "fail", Tags: fake.Tags{"aws:autoscaling:groupName": "none"}, PrivateIp: "0.2.2.2", VpcId: "1", State: runningState},
 			)},
-			autoscalingMockOutputs{
-				describeAutoScalingGroups: R(mockDASGOutput(map[string]asgtags{
+			fake.ASGOutputs{
+				DescribeAutoScalingGroups: fake.R(fake.MockDescribeAutoScalingGroupOutput(map[string]fake.ASGtags{
 					"asg1": {
 						"foo":                          "bar",
 						clusterIDTagPrefix + clusterID: resourceLifecycleOwned,
@@ -541,18 +543,18 @@ func TestUpdateAutoScalingGroupsAndInstances(tt *testing.T) {
 		},
 		{
 			"add-back-third-asg",
-			ec2MockOutputs{describeInstancesPages: mockDIPOutput(
+			fake.EC2Outputs{DescribeInstancesPages: fake.MockDescribeInstancesPagesOutput(
 				nil,
-				testInstance{id: "foo0", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.3", vpcId: "1", state: runningState},
-				testInstance{id: "foo1", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.4", vpcId: "1", state: runningState},
-				testInstance{id: "foo2", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.5", vpcId: "1", state: runningState},
-				testInstance{id: "bar1", tags: tags{"aws:autoscaling:groupName": "asg2"}, privateIp: "2.1.1.1", vpcId: "1", state: runningState},
-				testInstance{id: "bar2", tags: tags{"aws:autoscaling:groupName": "asg2"}, privateIp: "2.2.2.2", vpcId: "1", state: runningState},
-				testInstance{id: "baz1", tags: tags{"aws:autoscaling:groupName": "asg3"}, privateIp: "3.1.1.1", vpcId: "1", state: runningState},
-				testInstance{id: "sgl1", tags: tags{"Name": "node1"}, privateIp: "0.1.1.1", vpcId: "1", state: runningState},
+				fake.TestInstance{Id: "foo0", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.3", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "foo1", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.4", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "foo2", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.5", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "bar1", Tags: fake.Tags{"aws:autoscaling:groupName": "asg2"}, PrivateIp: "2.1.1.1", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "bar2", Tags: fake.Tags{"aws:autoscaling:groupName": "asg2"}, PrivateIp: "2.2.2.2", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "baz1", Tags: fake.Tags{"aws:autoscaling:groupName": "asg3"}, PrivateIp: "3.1.1.1", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "sgl1", Tags: fake.Tags{"Name": "node1"}, PrivateIp: "0.1.1.1", VpcId: "1", State: runningState},
 			)},
-			autoscalingMockOutputs{
-				describeAutoScalingGroups: R(mockDASGOutput(map[string]asgtags{
+			fake.ASGOutputs{
+				DescribeAutoScalingGroups: fake.R(fake.MockDescribeAutoScalingGroupOutput(map[string]fake.ASGtags{
 					"asg1": {
 						"foo":                          "bar",
 						clusterIDTagPrefix + clusterID: resourceLifecycleOwned,
@@ -575,14 +577,14 @@ func TestUpdateAutoScalingGroupsAndInstances(tt *testing.T) {
 		},
 		{
 			"remove-all-except-first-asg",
-			ec2MockOutputs{describeInstancesPages: mockDIPOutput(
+			fake.EC2Outputs{DescribeInstancesPages: fake.MockDescribeInstancesPagesOutput(
 				nil,
-				testInstance{id: "foo0", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.3", vpcId: "1", state: runningState},
-				testInstance{id: "foo1", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.4", vpcId: "1", state: runningState},
-				testInstance{id: "foo2", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.5", vpcId: "1", state: runningState},
+				fake.TestInstance{Id: "foo0", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.3", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "foo1", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.4", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "foo2", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.5", VpcId: "1", State: runningState},
 			)},
-			autoscalingMockOutputs{
-				describeAutoScalingGroups: R(mockDASGOutput(map[string]asgtags{"asg1": {
+			fake.ASGOutputs{
+				DescribeAutoScalingGroups: fake.R(fake.MockDescribeAutoScalingGroupOutput(map[string]fake.ASGtags{"asg1": {
 					"foo":                          "bar",
 					clusterIDTagPrefix + clusterID: resourceLifecycleOwned,
 				}}), nil),
@@ -596,13 +598,13 @@ func TestUpdateAutoScalingGroupsAndInstances(tt *testing.T) {
 		},
 		{
 			"add-remove-simultaneously",
-			ec2MockOutputs{describeInstancesPages: mockDIPOutput(
+			fake.EC2Outputs{DescribeInstancesPages: fake.MockDescribeInstancesPagesOutput(
 				nil,
-				testInstance{id: "foo0", tags: tags{"aws:autoscaling:groupName": "asg1"}, privateIp: "1.2.3.3", vpcId: "1", state: runningState},
-				testInstance{id: "sgl1", tags: tags{"Name": "node1"}, privateIp: "0.1.1.1", vpcId: "1", state: runningState},
+				fake.TestInstance{Id: "foo0", Tags: fake.Tags{"aws:autoscaling:groupName": "asg1"}, PrivateIp: "1.2.3.3", VpcId: "1", State: runningState},
+				fake.TestInstance{Id: "sgl1", Tags: fake.Tags{"Name": "node1"}, PrivateIp: "0.1.1.1", VpcId: "1", State: runningState},
 			)},
-			autoscalingMockOutputs{
-				describeAutoScalingGroups: R(mockDASGOutput(map[string]asgtags{"asg1": {
+			fake.ASGOutputs{
+				DescribeAutoScalingGroups: fake.R(fake.MockDescribeAutoScalingGroupOutput(map[string]fake.ASGtags{"asg1": {
 					"foo":                          "bar",
 					clusterIDTagPrefix + clusterID: resourceLifecycleOwned,
 				}}), nil),
@@ -616,8 +618,8 @@ func TestUpdateAutoScalingGroupsAndInstances(tt *testing.T) {
 		},
 	} {
 		tt.Run(fmt.Sprintf("%v", test.name), func(t *testing.T) {
-			a.ec2 = &mockEc2Client{outputs: test.ec2responses}
-			a.autoscaling = &mockAutoScalingClient{outputs: test.asgresponses}
+			a.ec2 = &fake.EC2Client{Outputs: test.ec2responses}
+			a.autoscaling = &fake.ASGClient{Outputs: test.asgresponses}
 			err := a.UpdateAutoScalingGroupsAndInstances()
 			if test.wantError && err == nil {
 				t.Errorf("expected error, got nothing")
@@ -953,11 +955,11 @@ func TestWithxlbHealthyThresholdCount(t *testing.T) {
 func TestAdapter_SetTargetsOnCNITargetGroups(t *testing.T) {
 	tgARNs := []string{"asg1"}
 	thOut := elbv2.DescribeTargetHealthOutput{TargetHealthDescriptions: []*elbv2.TargetHealthDescription{}}
-	m := &mockElbv2Client{
-		outputs: elbv2MockOutputs{
-			describeTargetHealth: &apiResponse{response: &thOut, err: nil},
-			registerTargets:      R(mockDTOutput(), nil),
-			deregisterTargets:    R(mockDTOutput(), nil),
+	m := &fake.ELBv2Client{
+		Outputs: fake.ELBv2Outputs{
+			DescribeTargetHealth: fake.R(&thOut, nil),
+			RegisterTargets:      fake.R(fake.MockDeregisterTargetsOutput(), nil),
+			DeregisterTargets:    fake.R(fake.MockDeregisterTargetsOutput(), nil),
 		},
 	}
 	a := &Adapter{elbv2: m, TargetCNI: &TargetCNIconfig{}}
@@ -967,22 +969,22 @@ func TestAdapter_SetTargetsOnCNITargetGroups(t *testing.T) {
 		require.Equal(t, []*elbv2.RegisterTargetsInput{{
 			TargetGroupArn: aws.String("asg1"),
 			Targets:        []*elbv2.TargetDescription{{Id: aws.String("1.1.1.1")}},
-		}}, m.rtinputs)
-		require.Equal(t, []*elbv2.DeregisterTargetsInput(nil), m.dtinputs)
+		}}, m.Rtinputs)
+		require.Equal(t, []*elbv2.DeregisterTargetsInput(nil), m.Dtinputs)
 	})
 
 	t.Run("two new endpoints, registers the new EPs only", func(t *testing.T) {
 		thOut = elbv2.DescribeTargetHealthOutput{TargetHealthDescriptions: []*elbv2.TargetHealthDescription{
 			{Target: &elbv2.TargetDescription{Id: aws.String("1.1.1.1")}}},
 		}
-		m.rtinputs, m.dtinputs = nil, nil
+		m.Rtinputs, m.Dtinputs = nil, nil
 
 		require.NoError(t, a.SetTargetsOnCNITargetGroups([]string{"1.1.1.1", "2.2.2.2", "3.3.3.3"}, tgARNs))
 		require.Equal(t, []*elbv2.TargetDescription{
 			{Id: aws.String("2.2.2.2")},
 			{Id: aws.String("3.3.3.3")},
-		}, m.rtinputs[0].Targets)
-		require.Equal(t, []*elbv2.DeregisterTargetsInput(nil), m.dtinputs)
+		}, m.Rtinputs[0].Targets)
+		require.Equal(t, []*elbv2.DeregisterTargetsInput(nil), m.Dtinputs)
 	})
 
 	t.Run("removing one endpoint, causing deregistration of it", func(t *testing.T) {
@@ -991,24 +993,24 @@ func TestAdapter_SetTargetsOnCNITargetGroups(t *testing.T) {
 			{Target: &elbv2.TargetDescription{Id: aws.String("2.2.2.2")}},
 			{Target: &elbv2.TargetDescription{Id: aws.String("3.3.3.3")}},
 		}}
-		m.rtinputs, m.dtinputs = nil, nil
+		m.Rtinputs, m.Dtinputs = nil, nil
 
 		require.NoError(t, a.SetTargetsOnCNITargetGroups([]string{"1.1.1.1", "3.3.3.3"}, tgARNs))
-		require.Equal(t, []*elbv2.RegisterTargetsInput(nil), m.rtinputs)
-		require.Equal(t, []*elbv2.TargetDescription{{Id: aws.String("2.2.2.2")}}, m.dtinputs[0].Targets)
+		require.Equal(t, []*elbv2.RegisterTargetsInput(nil), m.Rtinputs)
+		require.Equal(t, []*elbv2.TargetDescription{{Id: aws.String("2.2.2.2")}}, m.Dtinputs[0].Targets)
 	})
 
-	t.Run("restoring desired state after external manipulation, adding and removing one", func(t *testing.T) {
+	t.Run("restoring desired State after external manipulation, adding and removing one", func(t *testing.T) {
 		thOut = elbv2.DescribeTargetHealthOutput{TargetHealthDescriptions: []*elbv2.TargetHealthDescription{
 			{Target: &elbv2.TargetDescription{Id: aws.String("1.1.1.1")}},
 			{Target: &elbv2.TargetDescription{Id: aws.String("2.2.2.2")}},
 			{Target: &elbv2.TargetDescription{Id: aws.String("4.4.4.4")}},
 		}}
-		m.rtinputs, m.dtinputs = nil, nil
+		m.Rtinputs, m.Dtinputs = nil, nil
 
 		require.NoError(t, a.SetTargetsOnCNITargetGroups([]string{"1.1.1.1", "2.2.2.2", "3.3.3.3"}, tgARNs))
-		require.Equal(t, []*elbv2.TargetDescription{{Id: aws.String("3.3.3.3")}}, m.rtinputs[0].Targets)
-		require.Equal(t, []*elbv2.TargetDescription{{Id: aws.String("4.4.4.4")}}, m.dtinputs[0].Targets)
+		require.Equal(t, []*elbv2.TargetDescription{{Id: aws.String("3.3.3.3")}}, m.Rtinputs[0].Targets)
+		require.Equal(t, []*elbv2.TargetDescription{{Id: aws.String("4.4.4.4")}}, m.Dtinputs[0].Targets)
 	})
 }
 
diff --git a/aws/asg_test.go b/aws/asg_test.go
index 87e825b..86e0d30 100644
--- a/aws/asg_test.go
+++ b/aws/asg_test.go
@@ -10,9 +10,9 @@ import (
 	"github.com/aws/aws-sdk-go/service/elbv2"
 
 	"github.com/stretchr/testify/assert"
-)
 
-type asgtags map[string]string
+	"github.com/zalando-incubator/kube-ingress-aws-controller/aws/fake"
+)
 
 func mockAutoScalingGroupDetails(name string, tags map[string]string) *autoScalingGroupDetails {
 	return &autoScalingGroupDetails{
@@ -26,15 +26,15 @@ func TestGetAutoScalingGroupByName(t *testing.T) {
 	for _, test := range []struct {
 		name      string
 		givenName string
-		responses autoscalingMockOutputs
+		responses fake.ASGOutputs
 		want      *autoScalingGroupDetails
 		wantError bool
 	}{
 		{
 			"success-call-single-asg",
 			"foo",
-			autoscalingMockOutputs{
-				describeAutoScalingGroups: R(mockDASGOutput(map[string]asgtags{"foo": {"bar": "baz"}}), nil),
+			fake.ASGOutputs{
+				DescribeAutoScalingGroups: fake.R(fake.MockDescribeAutoScalingGroupOutput(map[string]fake.ASGtags{"foo": {"bar": "baz"}}), nil),
 			},
 			mockAutoScalingGroupDetails("foo", map[string]string{"bar": "baz"}),
 			false,
@@ -42,8 +42,8 @@ func TestGetAutoScalingGroupByName(t *testing.T) {
 		{
 			"success-call-multiple-asg",
 			"d",
-			autoscalingMockOutputs{
-				describeAutoScalingGroups: R(mockDASGOutput(map[string]asgtags{
+			fake.ASGOutputs{
+				DescribeAutoScalingGroups: fake.R(fake.MockDescribeAutoScalingGroupOutput(map[string]fake.ASGtags{
 					"a": {"b": "c"},
 					"d": {"e": "f"},
 				}), nil),
@@ -54,8 +54,8 @@ func TestGetAutoScalingGroupByName(t *testing.T) {
 		{
 			"fail-to-match-single-asg",
 			"miss",
-			autoscalingMockOutputs{
-				describeAutoScalingGroups: R(mockDASGOutput(map[string]asgtags{"foo": {"bar": "baz"}}), nil),
+			fake.ASGOutputs{
+				DescribeAutoScalingGroups: fake.R(fake.MockDescribeAutoScalingGroupOutput(map[string]fake.ASGtags{"foo": {"bar": "baz"}}), nil),
 			},
 			nil,
 			true,
@@ -63,8 +63,8 @@ func TestGetAutoScalingGroupByName(t *testing.T) {
 		{
 			"fail-to-match-multiple-asg",
 			"miss",
-			autoscalingMockOutputs{
-				describeAutoScalingGroups: R(mockDASGOutput(map[string]asgtags{
+			fake.ASGOutputs{
+				DescribeAutoScalingGroups: fake.R(fake.MockDescribeAutoScalingGroupOutput(map[string]fake.ASGtags{
 					"a": {"b": "c"},
 					"d": {"e": "f"},
 				}), nil),
@@ -75,13 +75,13 @@ func TestGetAutoScalingGroupByName(t *testing.T) {
 		{
 			"autoscaling-api-failure",
 			"dontcare",
-			autoscalingMockOutputs{describeAutoScalingGroups: R(nil, errDummy)},
+			fake.ASGOutputs{DescribeAutoScalingGroups: fake.R(nil, fake.ErrDummy)},
 			nil,
 			true,
 		},
 	} {
 		t.Run(fmt.Sprintf("%v", test.name), func(t *testing.T) {
-			mockSvc := &mockAutoScalingClient{outputs: test.responses}
+			mockSvc := &fake.ASGClient{Outputs: test.responses}
 			got, err := getAutoScalingGroupByName(mockSvc, test.givenName)
 
 			if test.wantError {
@@ -104,15 +104,15 @@ func TestGetAutoScalingGroupsByName(t *testing.T) {
 	for _, test := range []struct {
 		name       string
 		givenNames []string
-		responses  autoscalingMockOutputs
+		responses  fake.ASGOutputs
 		want       map[string]*autoScalingGroupDetails
 		wantError  bool
 	}{
 		{
 			"success-call-single-asg",
 			[]string{"foo"},
-			autoscalingMockOutputs{
-				describeAutoScalingGroups: R(mockDASGOutput(map[string]asgtags{"foo": {"bar": "baz"}}), nil),
+			fake.ASGOutputs{
+				DescribeAutoScalingGroups: fake.R(fake.MockDescribeAutoScalingGroupOutput(map[string]fake.ASGtags{"foo": {"bar": "baz"}}), nil),
 			},
 			map[string]*autoScalingGroupDetails{
 				"foo": mockAutoScalingGroupDetails("foo", map[string]string{"bar": "baz"}),
@@ -122,8 +122,8 @@ func TestGetAutoScalingGroupsByName(t *testing.T) {
 		{
 			"success-call-multiple-asg",
 			[]string{"a", "d"},
-			autoscalingMockOutputs{
-				describeAutoScalingGroups: R(mockDASGOutput(map[string]asgtags{
+			fake.ASGOutputs{
+				DescribeAutoScalingGroups: fake.R(fake.MockDescribeAutoScalingGroupOutput(map[string]fake.ASGtags{
 					"a": {"b": "c"},
 					"d": {"e": "f"},
 				}), nil),
@@ -137,8 +137,8 @@ func TestGetAutoScalingGroupsByName(t *testing.T) {
 		{
 			"fail-to-match-single-asg",
 			[]string{"miss"},
-			autoscalingMockOutputs{
-				describeAutoScalingGroups: R(mockDASGOutput(map[string]asgtags{"foo": {"bar": "baz"}}), nil),
+			fake.ASGOutputs{
+				DescribeAutoScalingGroups: fake.R(fake.MockDescribeAutoScalingGroupOutput(map[string]fake.ASGtags{"foo": {"bar": "baz"}}), nil),
 			},
 			nil,
 			true,
@@ -146,8 +146,8 @@ func TestGetAutoScalingGroupsByName(t *testing.T) {
 		{
 			"fail-to-match-multiple-asg",
 			[]string{"miss", "miss2"},
-			autoscalingMockOutputs{
-				describeAutoScalingGroups: R(mockDASGOutput(map[string]asgtags{
+			fake.ASGOutputs{
+				DescribeAutoScalingGroups: fake.R(fake.MockDescribeAutoScalingGroupOutput(map[string]fake.ASGtags{
 					"a": {"b": "c"},
 					"d": {"e": "f"},
 				}), nil),
@@ -158,13 +158,13 @@ func TestGetAutoScalingGroupsByName(t *testing.T) {
 		{
 			"autoscaling-api-failure",
 			[]string{"dontcare"},
-			autoscalingMockOutputs{describeAutoScalingGroups: R(nil, errDummy)},
+			fake.ASGOutputs{DescribeAutoScalingGroups: fake.R(nil, fake.ErrDummy)},
 			nil,
 			true,
 		},
 	} {
 		t.Run(fmt.Sprintf("%v", test.name), func(t *testing.T) {
-			mockSvc := &mockAutoScalingClient{outputs: test.responses}
+			mockSvc := &fake.ASGClient{Outputs: test.responses}
 			got, err := getAutoScalingGroupsByName(mockSvc, test.givenNames)
 
 			if test.wantError {
@@ -187,79 +187,79 @@ func TestAttach(t *testing.T) {
 	for _, test := range []struct {
 		name               string
 		targetGroups       []string
-		autoscalingOutputs autoscalingMockOutputs
-		autoscalingInputs  autoscalingMockInputs
-		elbv2Response      elbv2MockOutputs
+		autoscalingOutputs fake.ASGOutputs
+		autoscalingInputs  fake.ASGInputs
+		elbv2Response      fake.ELBv2Outputs
 		ownerTags          map[string]string
 		wantError          bool
 	}{
 		{
 			name:         "describe-lb-target-groups-failed",
 			targetGroups: []string{"foo"},
-			autoscalingOutputs: autoscalingMockOutputs{
-				describeLoadBalancerTargetGroups: R(nil, errDummy),
+			autoscalingOutputs: fake.ASGOutputs{
+				DescribeLoadBalancerTargetGroups: fake.R(nil, fake.ErrDummy),
 			},
 			wantError: true,
 		},
 		{
 			name:         "describe-all-target-groups-failed",
 			targetGroups: []string{"foo"},
-			autoscalingOutputs: autoscalingMockOutputs{
-				attachLoadBalancerTargetGroups: R(nil, nil),
-				describeLoadBalancerTargetGroups: R(&autoscaling.DescribeLoadBalancerTargetGroupsOutput{
+			autoscalingOutputs: fake.ASGOutputs{
+				AttachLoadBalancerTargetGroups: fake.R(nil, nil),
+				DescribeLoadBalancerTargetGroups: fake.R(&autoscaling.DescribeLoadBalancerTargetGroupsOutput{
 					LoadBalancerTargetGroups: []*autoscaling.LoadBalancerTargetGroupState{
 						{
 							LoadBalancerTargetGroupARN: aws.String("foo"),
 						},
 					},
 				}, nil),
-				detachLoadBalancerTargetGroups: R(nil, nil),
+				DetachLoadBalancerTargetGroups: fake.R(nil, nil),
 			},
-			elbv2Response: elbv2MockOutputs{
-				describeTargetGroups: R(nil, errDummy),
+			elbv2Response: fake.ELBv2Outputs{
+				DescribeTargetGroups: fake.R(nil, fake.ErrDummy),
 			},
 			wantError: true,
 		},
 		{
 			name:         "describe-tags-failed",
 			targetGroups: []string{"foo"},
-			autoscalingOutputs: autoscalingMockOutputs{
-				attachLoadBalancerTargetGroups: R(nil, nil),
-				describeLoadBalancerTargetGroups: R(&autoscaling.DescribeLoadBalancerTargetGroupsOutput{
+			autoscalingOutputs: fake.ASGOutputs{
+				AttachLoadBalancerTargetGroups: fake.R(nil, nil),
+				DescribeLoadBalancerTargetGroups: fake.R(&autoscaling.DescribeLoadBalancerTargetGroupsOutput{
 					LoadBalancerTargetGroups: []*autoscaling.LoadBalancerTargetGroupState{
 						{
 							LoadBalancerTargetGroupARN: aws.String("foo"),
 						},
 					},
 				}, nil),
-				detachLoadBalancerTargetGroups: R(nil, nil),
+				DetachLoadBalancerTargetGroups: fake.R(nil, nil),
 			},
-			elbv2Response: elbv2MockOutputs{
-				describeTargetGroups: R(&elbv2.DescribeTargetGroupsOutput{
+			elbv2Response: fake.ELBv2Outputs{
+				DescribeTargetGroups: fake.R(&elbv2.DescribeTargetGroupsOutput{
 					TargetGroups: []*elbv2.TargetGroup{
 						{
 							TargetGroupArn: aws.String("foo"),
 						},
 					},
 				}, nil),
-				describeTags: R(nil, errDummy),
+				DescribeTags: fake.R(nil, fake.ErrDummy),
 			},
 			wantError: true,
 		},
 		{
 			name:         "success-attach",
 			targetGroups: []string{"foo"},
-			autoscalingOutputs: autoscalingMockOutputs{
-				attachLoadBalancerTargetGroups: R(nil, nil),
-				describeLoadBalancerTargetGroups: R(&autoscaling.DescribeLoadBalancerTargetGroupsOutput{
+			autoscalingOutputs: fake.ASGOutputs{
+				AttachLoadBalancerTargetGroups: fake.R(nil, nil),
+				DescribeLoadBalancerTargetGroups: fake.R(&autoscaling.DescribeLoadBalancerTargetGroupsOutput{
 					LoadBalancerTargetGroups: []*autoscaling.LoadBalancerTargetGroupState{
 						{
 							LoadBalancerTargetGroupARN: aws.String("foo"),
 						},
 					},
 				}, nil)},
-			elbv2Response: elbv2MockOutputs{
-				describeTargetGroups: R(&elbv2.DescribeTargetGroupsOutput{
+			elbv2Response: fake.ELBv2Outputs{
+				DescribeTargetGroups: fake.R(&elbv2.DescribeTargetGroupsOutput{
 					TargetGroups: []*elbv2.TargetGroup{
 						{
 							TargetGroupArn: aws.String("foo"),
@@ -267,7 +267,7 @@ func TestAttach(t *testing.T) {
 						},
 					},
 				}, nil),
-				describeTags: R(&elbv2.DescribeTagsOutput{
+				DescribeTags: fake.R(&elbv2.DescribeTagsOutput{
 					TagDescriptions: []*elbv2.TagDescription{
 						{
 							ResourceArn: aws.String("foo"),
@@ -287,9 +287,9 @@ func TestAttach(t *testing.T) {
 		{
 			name:         "failed-attach",
 			targetGroups: []string{"foo"},
-			autoscalingOutputs: autoscalingMockOutputs{
-				attachLoadBalancerTargetGroups: R(nil, errDummy),
-				describeLoadBalancerTargetGroups: R(&autoscaling.DescribeLoadBalancerTargetGroupsOutput{
+			autoscalingOutputs: fake.ASGOutputs{
+				AttachLoadBalancerTargetGroups: fake.R(nil, fake.ErrDummy),
+				DescribeLoadBalancerTargetGroups: fake.R(&autoscaling.DescribeLoadBalancerTargetGroupsOutput{
 					LoadBalancerTargetGroups: []*autoscaling.LoadBalancerTargetGroupState{
 						{
 							LoadBalancerTargetGroupARN: aws.String("foo"),
@@ -297,8 +297,8 @@ func TestAttach(t *testing.T) {
 					},
 				}, nil),
 			},
-			elbv2Response: elbv2MockOutputs{
-				describeTargetGroups: R(&elbv2.DescribeTargetGroupsOutput{
+			elbv2Response: fake.ELBv2Outputs{
+				DescribeTargetGroups: fake.R(&elbv2.DescribeTargetGroupsOutput{
 					TargetGroups: []*elbv2.TargetGroup{
 						{
 							TargetGroupArn: aws.String("foo"),
@@ -306,7 +306,7 @@ func TestAttach(t *testing.T) {
 						},
 					},
 				}, nil),
-				describeTags: R(&elbv2.DescribeTagsOutput{
+				DescribeTags: fake.R(&elbv2.DescribeTagsOutput{
 					TagDescriptions: []*elbv2.TagDescription{
 						{
 							ResourceArn: aws.String("foo"),
@@ -326,9 +326,9 @@ func TestAttach(t *testing.T) {
 		{
 			name:         "detach-obsolete",
 			targetGroups: []string{"foo"},
-			autoscalingOutputs: autoscalingMockOutputs{
-				attachLoadBalancerTargetGroups: R(nil, nil),
-				describeLoadBalancerTargetGroups: R(&autoscaling.DescribeLoadBalancerTargetGroupsOutput{
+			autoscalingOutputs: fake.ASGOutputs{
+				AttachLoadBalancerTargetGroups: fake.R(nil, nil),
+				DescribeLoadBalancerTargetGroups: fake.R(&autoscaling.DescribeLoadBalancerTargetGroupsOutput{
 					LoadBalancerTargetGroups: []*autoscaling.LoadBalancerTargetGroupState{
 						{
 							LoadBalancerTargetGroupARN: aws.String("foo"),
@@ -344,10 +344,10 @@ func TestAttach(t *testing.T) {
 						},
 					},
 				}, nil),
-				detachLoadBalancerTargetGroups: R(nil, nil),
+				DetachLoadBalancerTargetGroups: fake.R(nil, nil),
 			},
-			elbv2Response: elbv2MockOutputs{
-				describeTargetGroups: R(&elbv2.DescribeTargetGroupsOutput{
+			elbv2Response: fake.ELBv2Outputs{
+				DescribeTargetGroups: fake.R(&elbv2.DescribeTargetGroupsOutput{
 					TargetGroups: []*elbv2.TargetGroup{
 						{
 							TargetGroupArn: aws.String("foo"),
@@ -360,7 +360,7 @@ func TestAttach(t *testing.T) {
 						},
 					},
 				}, nil),
-				describeTags: R(&elbv2.DescribeTagsOutput{
+				DescribeTags: fake.R(&elbv2.DescribeTagsOutput{
 					TagDescriptions: []*elbv2.TagDescription{
 						{
 							ResourceArn: aws.String("foo"),
@@ -397,9 +397,9 @@ func TestAttach(t *testing.T) {
 		{
 			name:         "failed-detach",
 			targetGroups: []string{"foo"},
-			autoscalingOutputs: autoscalingMockOutputs{
-				attachLoadBalancerTargetGroups: R(nil, nil),
-				describeLoadBalancerTargetGroups: R(&autoscaling.DescribeLoadBalancerTargetGroupsOutput{
+			autoscalingOutputs: fake.ASGOutputs{
+				AttachLoadBalancerTargetGroups: fake.R(nil, nil),
+				DescribeLoadBalancerTargetGroups: fake.R(&autoscaling.DescribeLoadBalancerTargetGroupsOutput{
 					LoadBalancerTargetGroups: []*autoscaling.LoadBalancerTargetGroupState{
 						{
 							LoadBalancerTargetGroupARN: aws.String("foo"),
@@ -409,10 +409,10 @@ func TestAttach(t *testing.T) {
 						},
 					},
 				}, nil),
-				detachLoadBalancerTargetGroups: R(nil, errDummy),
+				DetachLoadBalancerTargetGroups: fake.R(nil, fake.ErrDummy),
 			},
-			elbv2Response: elbv2MockOutputs{
-				describeTargetGroups: R(&elbv2.DescribeTargetGroupsOutput{
+			elbv2Response: fake.ELBv2Outputs{
+				DescribeTargetGroups: fake.R(&elbv2.DescribeTargetGroupsOutput{
 					TargetGroups: []*elbv2.TargetGroup{
 						{
 							TargetGroupArn: aws.String("foo"),
@@ -422,7 +422,7 @@ func TestAttach(t *testing.T) {
 						},
 					},
 				}, nil),
-				describeTags: R(&elbv2.DescribeTagsOutput{
+				DescribeTags: fake.R(&elbv2.DescribeTagsOutput{
 					TagDescriptions: []*elbv2.TagDescription{
 						{
 							ResourceArn: aws.String("foo"),
@@ -451,9 +451,9 @@ func TestAttach(t *testing.T) {
 		{
 			name:         "attach ignores nonexistent target groups",
 			targetGroups: []string{"foo", "void", "bar", "blank"},
-			autoscalingOutputs: autoscalingMockOutputs{
-				attachLoadBalancerTargetGroups: R(nil, nil),
-				describeLoadBalancerTargetGroups: R(&autoscaling.DescribeLoadBalancerTargetGroupsOutput{
+			autoscalingOutputs: fake.ASGOutputs{
+				AttachLoadBalancerTargetGroups: fake.R(nil, nil),
+				DescribeLoadBalancerTargetGroups: fake.R(&autoscaling.DescribeLoadBalancerTargetGroupsOutput{
 					LoadBalancerTargetGroups: []*autoscaling.LoadBalancerTargetGroupState{
 						{
 							LoadBalancerTargetGroupARN: aws.String("foo"),
@@ -463,8 +463,8 @@ func TestAttach(t *testing.T) {
 						},
 					},
 				}, nil)},
-			elbv2Response: elbv2MockOutputs{
-				describeTargetGroups: R(&elbv2.DescribeTargetGroupsOutput{
+			elbv2Response: fake.ELBv2Outputs{
+				DescribeTargetGroups: fake.R(&elbv2.DescribeTargetGroupsOutput{
 					TargetGroups: []*elbv2.TargetGroup{
 						{
 							TargetGroupArn: aws.String("foo"),
@@ -476,7 +476,7 @@ func TestAttach(t *testing.T) {
 						},
 					},
 				}, nil),
-				describeTags: R(&elbv2.DescribeTagsOutput{
+				DescribeTags: fake.R(&elbv2.DescribeTagsOutput{
 					TagDescriptions: []*elbv2.TagDescription{
 						{
 							ResourceArn: aws.String("foo"),
@@ -489,8 +489,8 @@ func TestAttach(t *testing.T) {
 					},
 				}, nil),
 			},
-			autoscalingInputs: autoscalingMockInputs{
-				attachLoadBalancerTargetGroups: func(t *testing.T, input *autoscaling.AttachLoadBalancerTargetGroupsInput) {
+			autoscalingInputs: fake.ASGInputs{
+				AttachLoadBalancerTargetGroups: func(t *testing.T, input *autoscaling.AttachLoadBalancerTargetGroupsInput) {
 					assert.Equal(t, aws.String("asg-name"), input.AutoScalingGroupName)
 					assert.Equal(t, aws.StringSlice([]string{"foo", "bar"}), input.TargetGroupARNs)
 				},
@@ -500,8 +500,8 @@ func TestAttach(t *testing.T) {
 		},
 	} {
 		t.Run(test.name, func(t *testing.T) {
-			mockSvc := &mockAutoScalingClient{outputs: test.autoscalingOutputs, inputs: test.autoscalingInputs, t: t}
-			mockElbv2Svc := &mockElbv2Client{outputs: test.elbv2Response}
+			mockSvc := &fake.ASGClient{Outputs: test.autoscalingOutputs, Inputs: test.autoscalingInputs, T: t}
+			mockElbv2Svc := &fake.ELBv2Client{Outputs: test.elbv2Response}
 			err := updateTargetGroupsForAutoScalingGroup(mockSvc, mockElbv2Svc, test.targetGroups, "asg-name", test.ownerTags)
 			if test.wantError {
 				if err == nil {
@@ -519,16 +519,16 @@ func TestAttach(t *testing.T) {
 func TestDetach(t *testing.T) {
 	for _, test := range []struct {
 		name      string
-		responses autoscalingMockOutputs
+		responses fake.ASGOutputs
 		wantError bool
 	}{
-		{"success-detach", autoscalingMockOutputs{detachLoadBalancerTargetGroups: R(nil, nil)},
+		{"success-detach", fake.ASGOutputs{DetachLoadBalancerTargetGroups: fake.R(nil, nil)},
 			false},
-		{"failed-detach", autoscalingMockOutputs{detachLoadBalancerTargetGroups: R(nil, errDummy)},
+		{"failed-detach", fake.ASGOutputs{DetachLoadBalancerTargetGroups: fake.R(nil, fake.ErrDummy)},
 			true},
 	} {
 		t.Run(fmt.Sprintf("%v", test.name), func(t *testing.T) {
-			mockSvc := &mockAutoScalingClient{outputs: test.responses}
+			mockSvc := &fake.ASGClient{Outputs: test.responses}
 			err := detachTargetGroupsFromAutoScalingGroup(mockSvc, []string{"foo"}, "bar")
 			if test.wantError {
 				if err == nil {
@@ -718,7 +718,7 @@ func Test_categorizeTargetTypeInstance(t *testing.T) {
 				}
 			}
 
-			mockElbv2Svc := &mockElbv2Client{outputs: elbv2MockOutputs{describeTargetGroups: R(&elbv2.DescribeTargetGroupsOutput{TargetGroups: tgResponse}, nil)}}
+			mockElbv2Svc := &fake.ELBv2Client{Outputs: fake.ELBv2Outputs{DescribeTargetGroups: fake.R(&elbv2.DescribeTargetGroupsOutput{TargetGroups: tgResponse}, nil)}}
 			got, err := categorizeTargetTypeInstance(mockElbv2Svc, tg)
 			assert.NoError(t, err)
 			for k, v := range test.targetGroups {
diff --git a/aws/asgmock_test.go b/aws/asgmock_test.go
deleted file mode 100644
index 592ec5f..0000000
--- a/aws/asgmock_test.go
+++ /dev/null
@@ -1,80 +0,0 @@
-package aws
-
-import (
-	"testing"
-
-	"github.com/aws/aws-sdk-go/aws"
-	"github.com/aws/aws-sdk-go/service/autoscaling"
-	"github.com/aws/aws-sdk-go/service/autoscaling/autoscalingiface"
-)
-
-type autoscalingMockInputs struct {
-	attachLoadBalancerTargetGroups func(*testing.T, *autoscaling.AttachLoadBalancerTargetGroupsInput)
-}
-
-type autoscalingMockOutputs struct {
-	describeAutoScalingGroups        *apiResponse
-	attachLoadBalancerTargetGroups   *apiResponse
-	detachLoadBalancerTargetGroups   *apiResponse
-	describeLoadBalancerTargetGroups *apiResponse
-}
-
-type mockAutoScalingClient struct {
-	autoscalingiface.AutoScalingAPI
-	outputs autoscalingMockOutputs
-	inputs  autoscalingMockInputs
-	t       *testing.T
-}
-
-func (m *mockAutoScalingClient) DescribeAutoScalingGroups(*autoscaling.DescribeAutoScalingGroupsInput) (*autoscaling.DescribeAutoScalingGroupsOutput, error) {
-	if out, ok := m.outputs.describeAutoScalingGroups.response.(*autoscaling.DescribeAutoScalingGroupsOutput); ok {
-		return out, m.outputs.describeAutoScalingGroups.err
-	}
-	return nil, m.outputs.describeAutoScalingGroups.err
-}
-
-func (m *mockAutoScalingClient) DescribeAutoScalingGroupsPages(_ *autoscaling.DescribeAutoScalingGroupsInput, fn func(*autoscaling.DescribeAutoScalingGroupsOutput, bool) bool) error {
-	if out, ok := m.outputs.describeAutoScalingGroups.response.(*autoscaling.DescribeAutoScalingGroupsOutput); ok {
-		fn(out, true)
-	}
-	return m.outputs.describeAutoScalingGroups.err
-}
-
-func (m *mockAutoScalingClient) DescribeLoadBalancerTargetGroups(*autoscaling.DescribeLoadBalancerTargetGroupsInput) (*autoscaling.DescribeLoadBalancerTargetGroupsOutput, error) {
-	if out, ok := m.outputs.describeLoadBalancerTargetGroups.response.(*autoscaling.DescribeLoadBalancerTargetGroupsOutput); ok {
-		return out, m.outputs.describeLoadBalancerTargetGroups.err
-	}
-	return nil, m.outputs.describeLoadBalancerTargetGroups.err
-}
-
-func (m *mockAutoScalingClient) AttachLoadBalancerTargetGroups(input *autoscaling.AttachLoadBalancerTargetGroupsInput) (*autoscaling.AttachLoadBalancerTargetGroupsOutput, error) {
-	if m.inputs.attachLoadBalancerTargetGroups != nil {
-		m.inputs.attachLoadBalancerTargetGroups(m.t, input)
-	}
-	return nil, m.outputs.attachLoadBalancerTargetGroups.err
-}
-
-func (m *mockAutoScalingClient) DetachLoadBalancerTargetGroups(*autoscaling.DetachLoadBalancerTargetGroupsInput) (*autoscaling.DetachLoadBalancerTargetGroupsOutput, error) {
-	return nil, m.outputs.detachLoadBalancerTargetGroups.err
-}
-
-func mockDASGOutput(asgs ...map[string]asgtags) *autoscaling.DescribeAutoScalingGroupsOutput {
-	groups := make([]*autoscaling.Group, 0)
-	for _, asg := range asgs {
-		for name, tags := range asg {
-			t := make([]*autoscaling.TagDescription, 0)
-			for k, v := range tags {
-				t = append(t, &autoscaling.TagDescription{
-					Key:   aws.String(k),
-					Value: aws.String(v),
-				})
-			}
-			g := &autoscaling.Group{
-				AutoScalingGroupName: aws.String(name),
-				Tags:                 t,
-			}
-			groups = append(groups, g)
-		}
-	}
-	return &autoscaling.DescribeAutoScalingGroupsOutput{AutoScalingGroups: groups}
-}
diff --git a/aws/cf_test.go b/aws/cf_test.go
index 603e1e3..46d4d4c 100644
--- a/aws/cf_test.go
+++ b/aws/cf_test.go
@@ -8,13 +8,15 @@ import (
 	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/service/cloudformation"
 	"github.com/stretchr/testify/assert"
+
+	"github.com/zalando-incubator/kube-ingress-aws-controller/aws/fake"
 )
 
 func TestCreatingStack(t *testing.T) {
 	for _, ti := range []struct {
 		name         string
 		givenSpec    stackSpec
-		givenOutputs cfMockOutputs
+		givenOutputs fake.CFOutputs
 		want         string
 		wantErr      bool
 	}{
@@ -29,21 +31,21 @@ func TestCreatingStack(t *testing.T) {
 					"arn-second":  time.Time{},
 				},
 			},
-			cfMockOutputs{createStack: R(mockCSOutput("fake-stack-id"), nil)},
+			fake.CFOutputs{CreateStack: fake.R(fake.MockCSOutput("fake-stack-id"), nil)},
 			"fake-stack-id",
 			false,
 		},
 		{
 			"successful-call",
 			stackSpec{name: "foo", securityGroupID: "bar", vpcID: "baz"},
-			cfMockOutputs{createStack: R(mockCSOutput("fake-stack-id"), nil)},
+			fake.CFOutputs{CreateStack: fake.R(fake.MockCSOutput("fake-stack-id"), nil)},
 			"fake-stack-id",
 			false,
 		},
 		{
 			"fail-call",
 			stackSpec{name: "foo", securityGroupID: "bar", vpcID: "baz"},
-			cfMockOutputs{createStack: R(nil, errDummy)},
+			fake.CFOutputs{CreateStack: fake.R(nil, fake.ErrDummy)},
 			"fake-stack-id",
 			true,
 		},
@@ -55,7 +57,7 @@ func TestCreatingStack(t *testing.T) {
 				vpcID:           "baz",
 				wafWebAclId:     "foo-bar-baz",
 			},
-			cfMockOutputs{createStack: R(mockCSOutput("fake-stack-id"), nil)},
+			fake.CFOutputs{CreateStack: fake.R(fake.MockCSOutput("fake-stack-id"), nil)},
 			"fake-stack-id",
 			false,
 		},
@@ -68,7 +70,7 @@ func TestCreatingStack(t *testing.T) {
 				loadbalancerType: LoadBalancerTypeApplication,
 				httpTargetPort:   7777,
 			},
-			cfMockOutputs{createStack: R(mockCSOutput("fake-stack-id"), nil)},
+			fake.CFOutputs{CreateStack: fake.R(fake.MockCSOutput("fake-stack-id"), nil)},
 			"fake-stack-id",
 			false,
 		},
@@ -81,13 +83,13 @@ func TestCreatingStack(t *testing.T) {
 				loadbalancerType: LoadBalancerTypeNetwork,
 				httpTargetPort:   8888,
 			},
-			cfMockOutputs{createStack: R(mockCSOutput("fake-stack-id"), nil)},
+			fake.CFOutputs{CreateStack: fake.R(fake.MockCSOutput("fake-stack-id"), nil)},
 			"fake-stack-id",
 			false,
 		},
 	} {
 		t.Run(ti.name, func(t *testing.T) {
-			c := &mockCloudFormationClient{outputs: ti.givenOutputs}
+			c := &fake.CFClient{Outputs: ti.givenOutputs}
 			got, err := createStack(c, &ti.givenSpec)
 			if ti.wantErr {
 				if !ti.wantErr {
@@ -106,7 +108,7 @@ func TestUpdatingStack(t *testing.T) {
 	for _, ti := range []struct {
 		name         string
 		givenSpec    stackSpec
-		givenOutputs cfMockOutputs
+		givenOutputs fake.CFOutputs
 		want         string
 		wantErr      bool
 	}{
@@ -121,21 +123,21 @@ func TestUpdatingStack(t *testing.T) {
 					"arn-second":  time.Time{},
 				},
 			},
-			cfMockOutputs{updateStack: R(mockUSOutput("fake-stack-id"), nil)},
+			fake.CFOutputs{UpdateStack: fake.R(fake.MockUSOutput("fake-stack-id"), nil)},
 			"fake-stack-id",
 			false,
 		},
 		{
 			"successful-call",
 			stackSpec{name: "foo", securityGroupID: "bar", vpcID: "baz"},
-			cfMockOutputs{updateStack: R(mockUSOutput("fake-stack-id"), nil)},
+			fake.CFOutputs{UpdateStack: fake.R(fake.MockUSOutput("fake-stack-id"), nil)},
 			"fake-stack-id",
 			false,
 		},
 		{
 			"fail-call",
 			stackSpec{name: "foo", securityGroupID: "bar", vpcID: "baz"},
-			cfMockOutputs{updateStack: R(nil, errDummy)},
+			fake.CFOutputs{UpdateStack: fake.R(nil, fake.ErrDummy)},
 			"fake-stack-id",
 			true,
 		},
@@ -147,7 +149,7 @@ func TestUpdatingStack(t *testing.T) {
 				vpcID:           "baz",
 				wafWebAclId:     "foo-bar-baz",
 			},
-			cfMockOutputs{updateStack: R(mockUSOutput("fake-stack-id"), nil)},
+			fake.CFOutputs{UpdateStack: fake.R(fake.MockUSOutput("fake-stack-id"), nil)},
 			"fake-stack-id",
 			false,
 		},
@@ -160,7 +162,7 @@ func TestUpdatingStack(t *testing.T) {
 				loadbalancerType: LoadBalancerTypeApplication,
 				httpTargetPort:   7777,
 			},
-			cfMockOutputs{updateStack: R(mockUSOutput("fake-stack-id"), nil)},
+			fake.CFOutputs{UpdateStack: fake.R(fake.MockUSOutput("fake-stack-id"), nil)},
 			"fake-stack-id",
 			false,
 		},
@@ -173,13 +175,13 @@ func TestUpdatingStack(t *testing.T) {
 				loadbalancerType: LoadBalancerTypeNetwork,
 				httpTargetPort:   8888,
 			},
-			cfMockOutputs{updateStack: R(mockUSOutput("fake-stack-id"), nil)},
+			fake.CFOutputs{UpdateStack: fake.R(fake.MockUSOutput("fake-stack-id"), nil)},
 			"fake-stack-id",
 			false,
 		},
 	} {
 		t.Run(ti.name, func(t *testing.T) {
-			c := &mockCloudFormationClient{outputs: ti.givenOutputs}
+			c := &fake.CFClient{Outputs: ti.givenOutputs}
 			got, err := updateStack(c, &ti.givenSpec)
 			if ti.wantErr {
 				if !ti.wantErr {
@@ -198,30 +200,30 @@ func TestDeleteStack(t *testing.T) {
 	for _, ti := range []struct {
 		msg          string
 		givenSpec    stackSpec
-		givenOutputs cfMockOutputs
+		givenOutputs fake.CFOutputs
 		wantErr      bool
 	}{
 		{
 			"delete-existing-stack",
 			stackSpec{name: "existing-stack-id"},
-			cfMockOutputs{
-				deleteStack:                 R(mockDeleteStackOutput("existing-stack-id"), nil),
-				updateTerminationProtection: R(nil, nil),
+			fake.CFOutputs{
+				DeleteStack:                 fake.R(fake.MockDeleteStackOutput("existing-stack-id"), nil),
+				UpdateTerminationProtection: fake.R(nil, nil),
 			},
 			false,
 		},
 		{
 			"delete-non-existing-stack",
 			stackSpec{name: "non-existing-stack-id"},
-			cfMockOutputs{
-				deleteStack:                 R(mockDeleteStackOutput("existing-stack-id"), nil),
-				updateTerminationProtection: R(nil, nil),
+			fake.CFOutputs{
+				DeleteStack:                 fake.R(fake.MockDeleteStackOutput("existing-stack-id"), nil),
+				UpdateTerminationProtection: fake.R(nil, nil),
 			},
 			false,
 		},
 	} {
 		t.Run(ti.msg, func(t *testing.T) {
-			c := &mockCloudFormationClient{outputs: ti.givenOutputs}
+			c := &fake.CFClient{Outputs: ti.givenOutputs}
 			err := deleteStack(c, ti.givenSpec.name)
 			haveErr := err != nil
 			if haveErr != ti.wantErr {
@@ -388,15 +390,15 @@ func TestConvertStackParameters(t *testing.T) {
 func TestFindManagedStacks(t *testing.T) {
 	for _, ti := range []struct {
 		name    string
-		given   cfMockOutputs
+		given   fake.CFOutputs
 		want    []*Stack
 		wantErr bool
 	}{
 		{
 			name: "successful-call",
-			given: cfMockOutputs{
-				describeStackPages: R(nil, nil),
-				describeStacks: R(&cloudformation.DescribeStacksOutput{
+			given: fake.CFOutputs{
+				DescribeStackPages: fake.R(nil, nil),
+				DescribeStacks: fake.R(&cloudformation.DescribeStacksOutput{
 					Stacks: []*cloudformation.Stack{
 						{
 							StackName:   aws.String("managed-stack-not-ready"),
@@ -529,9 +531,9 @@ func TestFindManagedStacks(t *testing.T) {
 		},
 		{
 			name: "no-ready-stacks",
-			given: cfMockOutputs{
-				describeStackPages: R(nil, nil),
-				describeStacks: R(&cloudformation.DescribeStacksOutput{
+			given: fake.CFOutputs{
+				DescribeStackPages: fake.R(nil, nil),
+				DescribeStacks: fake.R(&cloudformation.DescribeStacksOutput{
 					Stacks: []*cloudformation.Stack{
 						{
 							StackName:   aws.String("managed-stack-not-ready"),
@@ -590,24 +592,24 @@ func TestFindManagedStacks(t *testing.T) {
 		},
 		{
 			"failed-paging",
-			cfMockOutputs{
-				describeStackPages: R(nil, errDummy),
-				describeStacks:     R(&cloudformation.DescribeStacksOutput{}, nil),
+			fake.CFOutputs{
+				DescribeStackPages: fake.R(nil, fake.ErrDummy),
+				DescribeStacks:     fake.R(&cloudformation.DescribeStacksOutput{}, nil),
 			},
 			nil,
 			true,
 		},
 		{
 			"failed-describe-page",
-			cfMockOutputs{
-				describeStacks: R(nil, errDummy),
+			fake.CFOutputs{
+				DescribeStacks: fake.R(nil, fake.ErrDummy),
 			},
 			nil,
 			true,
 		},
 	} {
 		t.Run(ti.name, func(t *testing.T) {
-			c := &mockCloudFormationClient{outputs: ti.given}
+			c := &fake.CFClient{Outputs: ti.given}
 			got, err := findManagedStacks(c, "test-cluster", DefaultControllerID)
 			if err != nil {
 				if !ti.wantErr {
@@ -625,15 +627,15 @@ func TestFindManagedStacks(t *testing.T) {
 func TestGetStack(t *testing.T) {
 	for _, ti := range []struct {
 		name    string
-		given   cfMockOutputs
+		given   fake.CFOutputs
 		want    *Stack
 		wantErr bool
 	}{
 		{
 			name: "successful-call",
-			given: cfMockOutputs{
-				describeStackPages: R(nil, nil),
-				describeStacks: R(&cloudformation.DescribeStacksOutput{
+			given: fake.CFOutputs{
+				DescribeStackPages: fake.R(nil, nil),
+				DescribeStacks: fake.R(&cloudformation.DescribeStacksOutput{
 					Stacks: []*cloudformation.Stack{
 						{
 							StackName:   aws.String("managed-stack"),
@@ -670,9 +672,9 @@ func TestGetStack(t *testing.T) {
 		},
 		{
 			name: "successful-call-http-arn",
-			given: cfMockOutputs{
-				describeStackPages: R(nil, nil),
-				describeStacks: R(&cloudformation.DescribeStacksOutput{
+			given: fake.CFOutputs{
+				DescribeStackPages: fake.R(nil, nil),
+				DescribeStacks: fake.R(&cloudformation.DescribeStacksOutput{
 					Stacks: []*cloudformation.Stack{
 						{
 							StackName:   aws.String("managed-stack"),
@@ -710,9 +712,9 @@ func TestGetStack(t *testing.T) {
 		},
 		{
 			name: "no-ready-stacks",
-			given: cfMockOutputs{
-				describeStackPages: R(nil, nil),
-				describeStacks: R(&cloudformation.DescribeStacksOutput{
+			given: fake.CFOutputs{
+				DescribeStackPages: fake.R(nil, nil),
+				DescribeStacks: fake.R(&cloudformation.DescribeStacksOutput{
 					Stacks: []*cloudformation.Stack{},
 				}, nil),
 			},
@@ -721,24 +723,24 @@ func TestGetStack(t *testing.T) {
 		},
 		{
 			"failed-paging",
-			cfMockOutputs{
-				describeStackPages: R(nil, errDummy),
-				describeStacks:     R(&cloudformation.DescribeStacksOutput{}, nil),
+			fake.CFOutputs{
+				DescribeStackPages: fake.R(nil, fake.ErrDummy),
+				DescribeStacks:     fake.R(&cloudformation.DescribeStacksOutput{}, nil),
 			},
 			nil,
 			true,
 		},
 		{
 			"failed-describe-page",
-			cfMockOutputs{
-				describeStacks: R(nil, errDummy),
+			fake.CFOutputs{
+				DescribeStacks: fake.R(nil, fake.ErrDummy),
 			},
 			nil,
 			true,
 		},
 	} {
 		t.Run(ti.name, func(t *testing.T) {
-			c := &mockCloudFormationClient{outputs: ti.given}
+			c := &fake.CFClient{Outputs: ti.given}
 			s, err := getStack(c, "dontcare")
 			if err != nil {
 				if !ti.wantErr {
diff --git a/aws/cfmock_test.go b/aws/cfmock_test.go
deleted file mode 100644
index 57487ff..0000000
--- a/aws/cfmock_test.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package aws
-
-import (
-	"github.com/aws/aws-sdk-go/aws"
-	"github.com/aws/aws-sdk-go/service/cloudformation"
-	"github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface"
-)
-
-type cfMockOutputs struct {
-	describeStackPages          *apiResponse
-	describeStacks              *apiResponse
-	createStack                 *apiResponse
-	updateStack                 *apiResponse
-	deleteStack                 *apiResponse
-	updateTerminationProtection *apiResponse
-}
-
-type mockCloudFormationClient struct {
-	cloudformationiface.CloudFormationAPI
-	outputs cfMockOutputs
-}
-
-func (m *mockCloudFormationClient) DescribeStacksPages(in *cloudformation.DescribeStacksInput, fn func(*cloudformation.DescribeStacksOutput, bool) bool) (err error) {
-	if m.outputs.describeStackPages != nil {
-		err = m.outputs.describeStackPages.err
-	}
-	if err != nil {
-		return
-	}
-
-	if out, ok := m.outputs.describeStacks.response.(*cloudformation.DescribeStacksOutput); ok {
-		fn(out, true)
-	}
-	if m.outputs.describeStacks != nil {
-		err = m.outputs.describeStacks.err
-	}
-
-	return
-}
-
-func (m *mockCloudFormationClient) DescribeStacks(in *cloudformation.DescribeStacksInput) (*cloudformation.DescribeStacksOutput, error) {
-	if out, ok := m.outputs.describeStacks.response.(*cloudformation.DescribeStacksOutput); ok {
-		return out, m.outputs.describeStacks.err
-	}
-	return nil, m.outputs.describeStacks.err
-}
-
-func (m *mockCloudFormationClient) CreateStack(params *cloudformation.CreateStackInput) (*cloudformation.CreateStackOutput, error) {
-	if out, ok := m.outputs.createStack.response.(*cloudformation.CreateStackOutput); ok {
-		return out, m.outputs.createStack.err
-	}
-	return nil, m.outputs.createStack.err
-}
-
-func mockCSOutput(stackId string) *cloudformation.CreateStackOutput {
-	return &cloudformation.CreateStackOutput{
-		StackId: aws.String(stackId),
-	}
-}
-
-func (m *mockCloudFormationClient) UpdateStack(params *cloudformation.UpdateStackInput) (*cloudformation.UpdateStackOutput, error) {
-	if out, ok := m.outputs.updateStack.response.(*cloudformation.UpdateStackOutput); ok {
-		return out, m.outputs.updateStack.err
-	}
-	return nil, m.outputs.updateStack.err
-}
-
-func mockUSOutput(stackId string) *cloudformation.UpdateStackOutput {
-	return &cloudformation.UpdateStackOutput{
-		StackId: aws.String(stackId),
-	}
-}
-
-func (m *mockCloudFormationClient) DeleteStack(params *cloudformation.DeleteStackInput) (*cloudformation.DeleteStackOutput, error) {
-	if out, ok := m.outputs.deleteStack.response.(*cloudformation.DeleteStackOutput); ok {
-		return out, m.outputs.deleteStack.err
-	}
-	return nil, m.outputs.deleteStack.err
-}
-
-func mockDeleteStackOutput(stackId string) *cloudformation.DeleteStackOutput {
-	return &cloudformation.DeleteStackOutput{}
-}
-
-func (m *mockCloudFormationClient) UpdateTerminationProtection(params *cloudformation.UpdateTerminationProtectionInput) (*cloudformation.UpdateTerminationProtectionOutput, error) {
-	if out, ok := m.outputs.updateTerminationProtection.response.(*cloudformation.UpdateTerminationProtectionOutput); ok {
-		return out, m.outputs.updateTerminationProtection.err
-	}
-	return nil, m.outputs.updateTerminationProtection.err
-}
diff --git a/aws/ec2_test.go b/aws/ec2_test.go
index 634151b..99d7e0b 100644
--- a/aws/ec2_test.go
+++ b/aws/ec2_test.go
@@ -7,6 +7,8 @@ import (
 
 	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/service/ec2"
+
+	"github.com/zalando-incubator/kube-ingress-aws-controller/aws/fake"
 )
 
 func TestGetAutoScalingName(t *testing.T) {
@@ -31,29 +33,29 @@ func TestGetAutoScalingName(t *testing.T) {
 func TestFindingSecurityGroup(t *testing.T) {
 	for _, test := range []struct {
 		name      string
-		responses ec2MockOutputs
+		responses fake.EC2Outputs
 		want      *securityGroupDetails
 		wantError bool
 	}{
 		{
 			"success-find-sg",
-			ec2MockOutputs{
-				describeSecurityGroups: R(mockDSGOutput(map[string]string{"id": "foo"}), nil),
+			fake.EC2Outputs{
+				DescribeSecurityGroups: fake.R(fake.MockDescribeSecurityGroupsOutput(map[string]string{"id": "foo"}), nil),
 			},
 			&securityGroupDetails{id: "id", name: "foo"},
 			false,
 		},
 		{
 			"fail-no-security-groups",
-			ec2MockOutputs{describeSecurityGroups: R(mockDSGOutput(nil), nil)}, nil, true,
+			fake.EC2Outputs{DescribeSecurityGroups: fake.R(fake.MockDescribeSecurityGroupsOutput(nil), nil)}, nil, true,
 		},
 		{
 			"fail-with-aws-api-error",
-			ec2MockOutputs{describeSecurityGroups: R(nil, errDummy)}, nil, true,
+			fake.EC2Outputs{DescribeSecurityGroups: fake.R(nil, fake.ErrDummy)}, nil, true,
 		},
 	} {
 		t.Run(fmt.Sprintf("%v", test.name), func(t *testing.T) {
-			ec2 := &mockEc2Client{outputs: test.responses}
+			ec2 := &fake.EC2Client{Outputs: test.responses}
 			got, err := findSecurityGroupWithClusterID(ec2, "foo", "kube-ingress-aws-controller")
 			assertResultAndError(t, test.want, got, test.wantError, err)
 		})
@@ -119,45 +121,45 @@ func TestInstanceDetails(t *testing.T) {
 func TestGetInstanceDetails(t *testing.T) {
 	for _, test := range []struct {
 		name      string
-		responses ec2MockOutputs
+		responses fake.EC2Outputs
 		want      *instanceDetails
 		wantError bool
 	}{
 		{
 			"success-call",
-			ec2MockOutputs{describeInstances: R(mockDIOutput(
-				testInstance{id: "foo", tags: tags{"bar": "baz"}, state: runningState},
+			fake.EC2Outputs{DescribeInstances: fake.R(fake.MockDescribeInstancesOutput(
+				fake.TestInstance{Id: "foo", Tags: fake.Tags{"bar": "baz"}, State: runningState},
 			), nil)},
 			&instanceDetails{id: "foo", tags: map[string]string{"bar": "baz"}, running: true},
 			false,
 		},
 		{
 			"failed-state-match",
-			ec2MockOutputs{describeInstances: R(mockDIOutput(
-				testInstance{id: "foo1", tags: tags{"bar": "baz"}, state: 0},
-				testInstance{id: "foo2", tags: tags{"bar": "baz"}, state: 32}, // shutting-down
-				testInstance{id: "foo2", tags: tags{"bar": "baz"}, state: 48}, // terminated includes running?!?
-				testInstance{id: "foo2", tags: tags{"bar": "baz"}, state: 64}, // stopping
-				testInstance{id: "foo2", tags: tags{"bar": "baz"}, state: 80}, // stopped includes running?!?
+			fake.EC2Outputs{DescribeInstances: fake.R(fake.MockDescribeInstancesOutput(
+				fake.TestInstance{Id: "foo1", Tags: fake.Tags{"bar": "baz"}, State: 0},
+				fake.TestInstance{Id: "foo2", Tags: fake.Tags{"bar": "baz"}, State: 32}, // shutting-down
+				fake.TestInstance{Id: "foo2", Tags: fake.Tags{"bar": "baz"}, State: 48}, // terminated includes running?!?
+				fake.TestInstance{Id: "foo2", Tags: fake.Tags{"bar": "baz"}, State: 64}, // stopping
+				fake.TestInstance{Id: "foo2", Tags: fake.Tags{"bar": "baz"}, State: 80}, // stopped includes running?!?
 			), nil)},
 			nil,
 			true,
 		},
 		{
 			"nothing-returned-from-aws-api",
-			ec2MockOutputs{describeInstances: R(mockDIOutput(), nil)},
+			fake.EC2Outputs{DescribeInstances: fake.R(fake.MockDescribeInstancesOutput(), nil)},
 			nil,
 			true,
 		},
 		{
 			"aws-api-fail",
-			ec2MockOutputs{describeInstances: R(nil, errDummy)},
+			fake.EC2Outputs{DescribeInstances: fake.R(nil, fake.ErrDummy)},
 			nil,
 			true,
 		},
 	} {
 		t.Run(fmt.Sprintf("%v", test.name), func(t *testing.T) {
-			ec2 := &mockEc2Client{outputs: test.responses}
+			ec2 := &fake.EC2Client{Outputs: test.responses}
 			got, err := getInstanceDetails(ec2, "foo")
 			assertResultAndError(t, test.want, got, test.wantError, err)
 		})
@@ -167,20 +169,20 @@ func TestGetInstanceDetails(t *testing.T) {
 func TestGetSubnets(t *testing.T) {
 	for _, test := range []struct {
 		name      string
-		responses ec2MockOutputs
+		responses fake.EC2Outputs
 		want      []*subnetDetails
 		wantError bool
 	}{
 		{
 			"success-call-nofilter",
-			ec2MockOutputs{
-				describeSubnets: R(mockDSOutput(
-					testSubnet{id: "foo1", name: "bar1", az: "baz1", tags: map[string]string{elbRoleTagName: ""}},
-					testSubnet{id: "foo2", name: "bar2", az: "baz2"},
+			fake.EC2Outputs{
+				DescribeSubnets: fake.R(fake.MockDescribeSubnetsOutput(
+					fake.TestSubnet{Id: "foo1", Name: "bar1", Az: "baz1", Tags: map[string]string{elbRoleTagName: ""}},
+					fake.TestSubnet{Id: "foo2", Name: "bar2", Az: "baz2"},
 				), nil),
-				describeRouteTables: R(mockDRTOutput(
-					testRouteTable{subnetID: "foo1", gatewayIds: []string{"igw-foo1"}},
-					testRouteTable{subnetID: "mismatch", gatewayIds: []string{"igw-foo2"}, main: true},
+				DescribeRouteTables: fake.R(fake.MockDescribeRouteTableOutput(
+					fake.TestRouteTable{SubnetID: "foo1", GatewayIds: []string{"igw-foo1"}},
+					fake.TestRouteTable{SubnetID: "mismatch", GatewayIds: []string{"igw-foo2"}, Main: true},
 				), nil),
 			},
 			[]*subnetDetails{
@@ -191,14 +193,14 @@ func TestGetSubnets(t *testing.T) {
 		},
 		{
 			"success-call-filtered",
-			ec2MockOutputs{
-				describeSubnets: R(mockDSOutput(
-					testSubnet{id: "foo1", name: "bar1", az: "baz1", tags: map[string]string{elbRoleTagName: "", clusterIDTagPrefix + "bar": "shared"}},
-					testSubnet{id: "foo2", name: "bar2", az: "baz2", tags: map[string]string{clusterIDTagPrefix + "bar": "shared"}},
+			fake.EC2Outputs{
+				DescribeSubnets: fake.R(fake.MockDescribeSubnetsOutput(
+					fake.TestSubnet{Id: "foo1", Name: "bar1", Az: "baz1", Tags: map[string]string{elbRoleTagName: "", clusterIDTagPrefix + "bar": "shared"}},
+					fake.TestSubnet{Id: "foo2", Name: "bar2", Az: "baz2", Tags: map[string]string{clusterIDTagPrefix + "bar": "shared"}},
 				), nil),
-				describeRouteTables: R(mockDRTOutput(
-					testRouteTable{subnetID: "foo1", gatewayIds: []string{"igw-foo1"}},
-					testRouteTable{subnetID: "mismatch", gatewayIds: []string{"igw-foo2"}, main: true},
+				DescribeRouteTables: fake.R(fake.MockDescribeRouteTableOutput(
+					fake.TestRouteTable{SubnetID: "foo1", GatewayIds: []string{"igw-foo1"}},
+					fake.TestRouteTable{SubnetID: "mismatch", GatewayIds: []string{"igw-foo2"}, Main: true},
 				), nil),
 			},
 			[]*subnetDetails{
@@ -209,33 +211,33 @@ func TestGetSubnets(t *testing.T) {
 		},
 		{
 			"aws-sdk-failure-describing-subnets",
-			ec2MockOutputs{describeSubnets: R(nil, errDummy)}, nil, true,
+			fake.EC2Outputs{DescribeSubnets: fake.R(nil, fake.ErrDummy)}, nil, true,
 		},
 		{
 			"aws-sdk-failure-describing-route-tables",
-			ec2MockOutputs{
-				describeSubnets: R(mockDSOutput(
-					testSubnet{id: "foo1", name: "bar1", az: "baz1"},
-					testSubnet{id: "foo2", name: "bar2", az: "baz2"},
+			fake.EC2Outputs{
+				DescribeSubnets: fake.R(fake.MockDescribeSubnetsOutput(
+					fake.TestSubnet{Id: "foo1", Name: "bar1", Az: "baz1"},
+					fake.TestSubnet{Id: "foo2", Name: "bar2", Az: "baz2"},
 				), nil),
-				describeRouteTables: R(nil, errDummy),
+				DescribeRouteTables: fake.R(nil, fake.ErrDummy),
 			}, nil, true,
 		},
 		{
 			"failure-to-map-subnets",
-			ec2MockOutputs{
-				describeSubnets: R(mockDSOutput(
-					testSubnet{id: "foo1", name: "bar1", az: "baz1"},
+			fake.EC2Outputs{
+				DescribeSubnets: fake.R(fake.MockDescribeSubnetsOutput(
+					fake.TestSubnet{Id: "foo1", Name: "bar1", Az: "baz1"},
 				), nil),
-				describeRouteTables: R(mockDRTOutput(
-					testRouteTable{subnetID: "x", gatewayIds: []string{"y"}},
+				DescribeRouteTables: fake.R(fake.MockDescribeRouteTableOutput(
+					fake.TestRouteTable{SubnetID: "x", GatewayIds: []string{"y"}},
 				), nil),
 			},
 			nil, true,
 		},
 	} {
 		t.Run(fmt.Sprintf("%v", test.name), func(t *testing.T) {
-			ec2 := &mockEc2Client{outputs: test.responses}
+			ec2 := &fake.EC2Client{Outputs: test.responses}
 			got, err := getSubnets(ec2, "foo", "bar")
 			assertResultAndError(t, test.want, got, test.wantError, err)
 		})
@@ -246,7 +248,7 @@ func TestGetInstancesDetailsWithFilters(t *testing.T) {
 	for _, test := range []struct {
 		name      string
 		input     []*ec2.Filter
-		responses ec2MockOutputs
+		responses fake.EC2Outputs
 		want      map[string]*instanceDetails
 		wantError bool
 	}{
@@ -260,11 +262,11 @@ func TestGetInstancesDetailsWithFilters(t *testing.T) {
 					},
 				},
 			},
-			ec2MockOutputs{describeInstancesPages: mockDIPOutput(
+			fake.EC2Outputs{DescribeInstancesPages: fake.MockDescribeInstancesPagesOutput(
 				nil,
-				testInstance{id: "foo1", tags: tags{"bar": "baz"}, privateIp: "1.2.3.4", vpcId: "1", state: 16},
-				testInstance{id: "foo2", tags: tags{"bar": "baz"}, privateIp: "1.2.3.5", vpcId: "1", state: 32},
-				testInstance{id: "foo3", tags: tags{"aaa": "zzz"}, privateIp: "1.2.3.6", vpcId: "1", state: 80},
+				fake.TestInstance{Id: "foo1", Tags: fake.Tags{"bar": "baz"}, PrivateIp: "1.2.3.4", VpcId: "1", State: 16},
+				fake.TestInstance{Id: "foo2", Tags: fake.Tags{"bar": "baz"}, PrivateIp: "1.2.3.5", VpcId: "1", State: 32},
+				fake.TestInstance{Id: "foo3", Tags: fake.Tags{"aaa": "zzz"}, PrivateIp: "1.2.3.6", VpcId: "1", State: 80},
 			)},
 			map[string]*instanceDetails{
 				"foo1": &instanceDetails{id: "foo1", tags: map[string]string{"bar": "baz"}, ip: "1.2.3.4", vpcID: "1", running: true},
@@ -276,10 +278,10 @@ func TestGetInstancesDetailsWithFilters(t *testing.T) {
 		{
 			"success-empty-filters",
 			[]*ec2.Filter{},
-			ec2MockOutputs{describeInstancesPages: mockDIPOutput(
+			fake.EC2Outputs{DescribeInstancesPages: fake.MockDescribeInstancesPagesOutput(
 				nil,
-				testInstance{id: "foo1", tags: tags{"bar": "baz"}, privateIp: "1.2.3.4", vpcId: "1", state: 16},
-				testInstance{id: "foo3", tags: tags{"aaa": "zzz"}, privateIp: "1.2.3.6", vpcId: "1", state: 80},
+				fake.TestInstance{Id: "foo1", Tags: fake.Tags{"bar": "baz"}, PrivateIp: "1.2.3.4", VpcId: "1", State: 16},
+				fake.TestInstance{Id: "foo3", Tags: fake.Tags{"aaa": "zzz"}, PrivateIp: "1.2.3.6", VpcId: "1", State: 80},
 			)},
 			map[string]*instanceDetails{
 				"foo1": &instanceDetails{id: "foo1", tags: map[string]string{"bar": "baz"}, ip: "1.2.3.4", vpcID: "1", running: true},
@@ -297,7 +299,7 @@ func TestGetInstancesDetailsWithFilters(t *testing.T) {
 					},
 				},
 			},
-			ec2MockOutputs{describeInstancesPages: mockDIPOutput(nil)},
+			fake.EC2Outputs{DescribeInstancesPages: fake.MockDescribeInstancesPagesOutput(nil)},
 			map[string]*instanceDetails{},
 			false,
 		},
@@ -311,13 +313,13 @@ func TestGetInstancesDetailsWithFilters(t *testing.T) {
 					},
 				},
 			},
-			ec2MockOutputs{describeInstancesPages: mockDIPOutput(errDummy, testInstance{})},
+			fake.EC2Outputs{DescribeInstancesPages: fake.MockDescribeInstancesPagesOutput(fake.ErrDummy, fake.TestInstance{})},
 			nil,
 			true,
 		},
 	} {
 		t.Run(fmt.Sprintf("%v", test.name), func(t *testing.T) {
-			ec2 := &mockEc2Client{outputs: test.responses}
+			ec2 := &fake.EC2Client{Outputs: test.responses}
 			got, err := getInstancesDetailsWithFilters(ec2, test.input)
 			assertResultAndError(t, test.want, got, test.wantError, err)
 		})
diff --git a/aws/ec2mock_test.go b/aws/ec2mock_test.go
deleted file mode 100644
index 15b77b1..0000000
--- a/aws/ec2mock_test.go
+++ /dev/null
@@ -1,162 +0,0 @@
-package aws
-
-import (
-	"github.com/aws/aws-sdk-go/aws"
-	"github.com/aws/aws-sdk-go/service/ec2"
-	"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
-)
-
-const dipSplitSize = 2
-
-type ec2MockOutputs struct {
-	describeSecurityGroups *apiResponse
-	describeInstances      *apiResponse
-	describeInstancesPages []*apiResponse
-	describeSubnets        *apiResponse
-	describeRouteTables    *apiResponse
-}
-
-type mockEc2Client struct {
-	ec2iface.EC2API
-	outputs ec2MockOutputs
-}
-
-func (m *mockEc2Client) DescribeSecurityGroups(*ec2.DescribeSecurityGroupsInput) (*ec2.DescribeSecurityGroupsOutput, error) {
-	if out, ok := m.outputs.describeSecurityGroups.response.(*ec2.DescribeSecurityGroupsOutput); ok {
-		return out, m.outputs.describeSecurityGroups.err
-	}
-	return nil, m.outputs.describeSecurityGroups.err
-}
-
-func (m *mockEc2Client) DescribeInstances(*ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) {
-	if out, ok := m.outputs.describeInstances.response.(*ec2.DescribeInstancesOutput); ok {
-		return out, m.outputs.describeInstances.err
-	}
-	return nil, m.outputs.describeInstances.err
-}
-
-func (m *mockEc2Client) DescribeInstancesPages(params *ec2.DescribeInstancesInput, f func(*ec2.DescribeInstancesOutput, bool) bool) error {
-	for _, resp := range m.outputs.describeInstancesPages {
-		if out, ok := resp.response.(*ec2.DescribeInstancesOutput); ok {
-			f(out, true)
-		}
-	}
-	if len(m.outputs.describeInstancesPages) != 0 {
-		return m.outputs.describeInstancesPages[0].err
-	}
-	return nil
-}
-
-func (m *mockEc2Client) DescribeSubnets(*ec2.DescribeSubnetsInput) (*ec2.DescribeSubnetsOutput, error) {
-	if out, ok := m.outputs.describeSubnets.response.(*ec2.DescribeSubnetsOutput); ok {
-		return out, m.outputs.describeSubnets.err
-	}
-	return nil, m.outputs.describeSubnets.err
-}
-
-func (m *mockEc2Client) DescribeRouteTables(*ec2.DescribeRouteTablesInput) (*ec2.DescribeRouteTablesOutput, error) {
-	if out, ok := m.outputs.describeRouteTables.response.(*ec2.DescribeRouteTablesOutput); ok {
-		return out, m.outputs.describeRouteTables.err
-	}
-	return nil, m.outputs.describeRouteTables.err
-}
-
-func mockDSGOutput(sgs map[string]string) *ec2.DescribeSecurityGroupsOutput {
-	groups := make([]*ec2.SecurityGroup, 0)
-	for id, name := range sgs {
-		sg := &ec2.SecurityGroup{
-			GroupId:   aws.String(id),
-			GroupName: aws.String(name),
-		}
-		groups = append(groups, sg)
-	}
-	return &ec2.DescribeSecurityGroupsOutput{SecurityGroups: groups}
-}
-
-type testInstance struct {
-	id        string
-	tags      tags
-	privateIp string
-	vpcId     string
-	state     int64
-}
-
-func mockDIOutput(mockedInstances ...testInstance) *ec2.DescribeInstancesOutput {
-	instances := make([]*ec2.Instance, 0, len(mockedInstances))
-	for _, i := range mockedInstances {
-		tags := make([]*ec2.Tag, 0, len(i.tags))
-		for k, v := range i.tags {
-			tags = append(tags, &ec2.Tag{Key: aws.String(k), Value: aws.String(v)})
-		}
-		instance := &ec2.Instance{
-			InstanceId:       aws.String(i.id),
-			Tags:             tags,
-			State:            &ec2.InstanceState{Code: aws.Int64(i.state)},
-			PrivateIpAddress: aws.String(i.privateIp),
-			VpcId:            aws.String(i.vpcId),
-		}
-		instances = append(instances, instance)
-	}
-	return &ec2.DescribeInstancesOutput{Reservations: []*ec2.Reservation{{Instances: instances}}}
-}
-
-func mockDIPOutput(e error, mockedInstances ...testInstance) []*apiResponse {
-	pages := len(mockedInstances) / dipSplitSize
-	result := make([]*apiResponse, pages, pages+1)
-	for i := 0; i < pages; i++ {
-		result[i] = R(mockDIOutput(mockedInstances[i*dipSplitSize:(i+1)*dipSplitSize]...), e)
-	}
-	if len(mockedInstances)%dipSplitSize != 0 {
-		result = append(result, R(mockDIOutput(mockedInstances[pages*dipSplitSize:]...), e))
-	}
-	return result
-}
-
-type testSubnet struct {
-	id   string
-	az   string
-	name string
-	tags map[string]string
-}
-
-func mockDSOutput(mockedSubnets ...testSubnet) *ec2.DescribeSubnetsOutput {
-	subnets := make([]*ec2.Subnet, 0, len(mockedSubnets))
-	for _, subnet := range mockedSubnets {
-		s := &ec2.Subnet{
-			SubnetId:         aws.String(subnet.id),
-			AvailabilityZone: aws.String(subnet.az),
-			Tags: []*ec2.Tag{
-				{Key: aws.String(nameTag), Value: aws.String(subnet.name)},
-			},
-		}
-		for k, v := range subnet.tags {
-			s.Tags = append(s.Tags, &ec2.Tag{Key: aws.String(k), Value: aws.String(v)})
-		}
-		subnets = append(subnets, s)
-	}
-	return &ec2.DescribeSubnetsOutput{Subnets: subnets}
-}
-
-type testRouteTable struct {
-	subnetID   string
-	main       bool
-	gatewayIds []string
-}
-
-func mockDRTOutput(mockedRouteTables ...testRouteTable) *ec2.DescribeRouteTablesOutput {
-	routeTables := make([]*ec2.RouteTable, 0, len(mockedRouteTables))
-	for _, mrt := range mockedRouteTables {
-		routes := make([]*ec2.Route, 0, len(mrt.gatewayIds))
-		for _, gwID := range mrt.gatewayIds {
-			routes = append(routes, &ec2.Route{GatewayId: aws.String(gwID)})
-		}
-		rt := &ec2.RouteTable{
-			Associations: []*ec2.RouteTableAssociation{
-				{SubnetId: aws.String(mrt.subnetID), Main: aws.Bool(mrt.main)},
-			},
-			Routes: routes,
-		}
-		routeTables = append(routeTables, rt)
-	}
-	return &ec2.DescribeRouteTablesOutput{RouteTables: routeTables}
-}
diff --git a/aws/elbv2_test.go b/aws/elbv2_test.go
index b14cd5e..f68eb5f 100644
--- a/aws/elbv2_test.go
+++ b/aws/elbv2_test.go
@@ -7,6 +7,8 @@ import (
 	"testing"
 
 	"github.com/aws/aws-sdk-go/aws"
+
+	"github.com/zalando-incubator/kube-ingress-aws-controller/aws/fake"
 )
 
 type registerTargetsOnTargetGroupsInputTest struct {
@@ -20,17 +22,17 @@ type deregisterTargetsOnTargetGroupsInputTest struct {
 }
 
 func TestRegisterTargetsOnTargetGroups(t *testing.T) {
-	outputsSuccess := elbv2MockOutputs{
-		registerTargets: R(mockRTOutput(), nil),
+	outputsSuccess := fake.ELBv2Outputs{
+		RegisterTargets: fake.R(fake.MockRTOutput(), nil),
 	}
-	outputsError := elbv2MockOutputs{
-		registerTargets: R(mockRTOutput(), errDummy),
+	outputsError := fake.ELBv2Outputs{
+		RegisterTargets: fake.R(fake.MockRTOutput(), fake.ErrDummy),
 	}
 
 	for _, test := range []struct {
 		name      string
 		input     registerTargetsOnTargetGroupsInputTest
-		outputs   elbv2MockOutputs
+		outputs   fake.ELBv2Outputs
 		wantError bool
 	}{
 		{
@@ -80,7 +82,7 @@ func TestRegisterTargetsOnTargetGroups(t *testing.T) {
 		},
 	} {
 		t.Run(fmt.Sprintf("%v", test.name), func(t *testing.T) {
-			svc := &mockElbv2Client{outputs: test.outputs}
+			svc := &fake.ELBv2Client{Outputs: test.outputs}
 			err := registerTargetsOnTargetGroups(svc, test.input.targetGroupARNs, test.input.instances)
 			if test.wantError && err == nil {
 				t.Fatalf("expected error, got nothing")
@@ -92,7 +94,7 @@ func TestRegisterTargetsOnTargetGroups(t *testing.T) {
 				sort.Strings(test.input.targetGroupARNs)
 				sort.Strings(test.input.instances)
 				rtTargetsGroupARNs := make([]string, 0, len(test.input.targetGroupARNs))
-				for _, input := range svc.rtinputs {
+				for _, input := range svc.Rtinputs {
 					rtTargetsGroupARNs = append(rtTargetsGroupARNs, aws.StringValue(input.TargetGroupArn))
 					rtInstances := make([]string, len(input.Targets))
 					for j, tgt := range input.Targets {
@@ -113,17 +115,17 @@ func TestRegisterTargetsOnTargetGroups(t *testing.T) {
 }
 
 func TestDeregisterTargetsOnTargetGroups(t *testing.T) {
-	outputsSuccess := elbv2MockOutputs{
-		deregisterTargets: R(mockDTOutput(), nil),
+	outputsSuccess := fake.ELBv2Outputs{
+		DeregisterTargets: fake.R(fake.MockDeregisterTargetsOutput(), nil),
 	}
-	outputsError := elbv2MockOutputs{
-		deregisterTargets: R(mockDTOutput(), errDummy),
+	outputsError := fake.ELBv2Outputs{
+		DeregisterTargets: fake.R(fake.MockDeregisterTargetsOutput(), fake.ErrDummy),
 	}
 
 	for _, test := range []struct {
 		name      string
 		input     deregisterTargetsOnTargetGroupsInputTest
-		outputs   elbv2MockOutputs
+		outputs   fake.ELBv2Outputs
 		wantError bool
 	}{
 		{
@@ -173,7 +175,7 @@ func TestDeregisterTargetsOnTargetGroups(t *testing.T) {
 		},
 	} {
 		t.Run(fmt.Sprintf("%v", test.name), func(t *testing.T) {
-			svc := &mockElbv2Client{outputs: test.outputs}
+			svc := &fake.ELBv2Client{Outputs: test.outputs}
 			err := deregisterTargetsOnTargetGroups(svc, test.input.targetGroupARNs, test.input.instances)
 			if test.wantError && err == nil {
 				t.Fatalf("expected error, got nothing")
@@ -185,7 +187,7 @@ func TestDeregisterTargetsOnTargetGroups(t *testing.T) {
 				sort.Strings(test.input.targetGroupARNs)
 				sort.Strings(test.input.instances)
 				dtTargetsGroupARNs := make([]string, 0, len(test.input.targetGroupARNs))
-				for _, input := range svc.dtinputs {
+				for _, input := range svc.Dtinputs {
 					dtTargetsGroupARNs = append(dtTargetsGroupARNs, aws.StringValue(input.TargetGroupArn))
 					dtInstances := make([]string, len(input.Targets))
 					for j, tgt := range input.Targets {
diff --git a/aws/elbv2mock_test.go b/aws/elbv2mock_test.go
deleted file mode 100644
index 927844c..0000000
--- a/aws/elbv2mock_test.go
+++ /dev/null
@@ -1,68 +0,0 @@
-package aws
-
-import (
-	"github.com/aws/aws-sdk-go/aws"
-	"github.com/aws/aws-sdk-go/aws/request"
-	"github.com/aws/aws-sdk-go/service/elbv2"
-	"github.com/aws/aws-sdk-go/service/elbv2/elbv2iface"
-)
-
-type elbv2MockOutputs struct {
-	registerTargets      *apiResponse
-	deregisterTargets    *apiResponse
-	describeTags         *apiResponse
-	describeTargetGroups *apiResponse
-	describeTargetHealth *apiResponse
-}
-
-type mockElbv2Client struct {
-	elbv2iface.ELBV2API
-	outputs  elbv2MockOutputs
-	rtinputs []*elbv2.RegisterTargetsInput
-	dtinputs []*elbv2.DeregisterTargetsInput
-}
-
-func (m *mockElbv2Client) RegisterTargets(in *elbv2.RegisterTargetsInput) (*elbv2.RegisterTargetsOutput, error) {
-	m.rtinputs = append(m.rtinputs, in)
-	if out, ok := m.outputs.registerTargets.response.(*elbv2.RegisterTargetsOutput); ok {
-		return out, m.outputs.registerTargets.err
-	}
-	return nil, m.outputs.registerTargets.err
-}
-
-func mockRTOutput() *elbv2.RegisterTargetsOutput {
-	return &elbv2.RegisterTargetsOutput{}
-}
-
-func (m *mockElbv2Client) DeregisterTargets(in *elbv2.DeregisterTargetsInput) (*elbv2.DeregisterTargetsOutput, error) {
-	m.dtinputs = append(m.dtinputs, in)
-	if out, ok := m.outputs.deregisterTargets.response.(*elbv2.DeregisterTargetsOutput); ok {
-		return out, m.outputs.deregisterTargets.err
-	}
-	return nil, m.outputs.deregisterTargets.err
-}
-
-func (m *mockElbv2Client) DescribeTags(tags *elbv2.DescribeTagsInput) (*elbv2.DescribeTagsOutput, error) {
-	if out, ok := m.outputs.describeTags.response.(*elbv2.DescribeTagsOutput); ok {
-		return out, m.outputs.describeTags.err
-	}
-	return nil, m.outputs.describeTags.err
-}
-
-func (m *mockElbv2Client) DescribeTargetGroupsPagesWithContext(ctx aws.Context, in *elbv2.DescribeTargetGroupsInput, f func(resp *elbv2.DescribeTargetGroupsOutput, lastPage bool) bool, opt ...request.Option) error {
-	if out, ok := m.outputs.describeTargetGroups.response.(*elbv2.DescribeTargetGroupsOutput); ok {
-		f(out, true)
-	}
-	return m.outputs.describeTargetGroups.err
-}
-
-func (m *mockElbv2Client) DescribeTargetHealth(*elbv2.DescribeTargetHealthInput) (*elbv2.DescribeTargetHealthOutput, error) {
-	if out, ok := m.outputs.describeTargetHealth.response.(*elbv2.DescribeTargetHealthOutput); ok {
-		return out, m.outputs.describeTargetHealth.err
-	}
-	return nil, m.outputs.describeTargetHealth.err
-}
-
-func mockDTOutput() *elbv2.DeregisterTargetsOutput {
-	return &elbv2.DeregisterTargetsOutput{}
-}
diff --git a/aws/mocks_test.go b/aws/mocks_test.go
deleted file mode 100644
index e8a7d37..0000000
--- a/aws/mocks_test.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package aws
-
-import "errors"
-
-type apiResponse struct {
-	response interface{}
-	err      error
-}
-
-type tags map[string]string
-
-var errDummy = errors.New("fail")
-
-func R(r interface{}, e error) *apiResponse {
-	return &apiResponse{response: r, err: e}
-}
diff --git a/certs/matching_test.go b/certs/matching_test.go
index 5be05af..b26c6a9 100644
--- a/certs/matching_test.go
+++ b/certs/matching_test.go
@@ -22,6 +22,7 @@ type caInfra struct {
 	sync.Once
 	err       error
 	chainKey  *rsa.PrivateKey
+	roots     *x509.CertPool
 	chainCert *x509.Certificate
 }
 
@@ -83,8 +84,8 @@ func createDummyCertDetail(t *testing.T, arn string, altNames []string, notBefor
 			ca.err = fmt.Errorf("unable to parse CA certificate: %v", err)
 			return
 		}
-		roots = x509.NewCertPool()
-		roots.AddCert(caReparsed)
+		ca.roots = x509.NewCertPool()
+		ca.roots.AddCert(caReparsed)
 
 		chainKey, err := rsa.GenerateKey(rand.Reader, 2048)
 		if err != nil {
@@ -143,7 +144,8 @@ func createDummyCertDetail(t *testing.T, arn string, altNames []string, notBefor
 		require.NoErrorf(t, err, "unable to parse certificate")
 	}
 
-	return NewCertificate(arn, reparsed, []*x509.Certificate{ca.chainCert})
+	c := NewCertificate(arn, reparsed, []*x509.Certificate{ca.chainCert})
+	return c.WithRoots(ca.roots)
 }
 
 type certCondition func(error, *CertificateSummary, *CertificateSummary) bool
diff --git a/testdata/simple_alb/expected.cf b/testdata/simple_alb/expected.cf
new file mode 100644
index 0000000..7e9378d
--- /dev/null
+++ b/testdata/simple_alb/expected.cf
@@ -0,0 +1,219 @@
+{
+    "AWSTemplateFormatVersion": "2010-09-09",
+    "Description": "Load Balancer for Kubernetes Ingress",
+    "Parameters": {
+        "HTTP2": {
+            "Type": "String",
+            "Default": "true",
+            "Description": "H2 Enabled"
+        },
+        "IpAddressType": {
+            "Type": "String",
+            "Default": "ipv4",
+            "Description": "IP Address Type, 'ipv4' or 'dualstack'"
+        },
+        "ListenerSslPolicyParameter": {
+            "Type": "String",
+            "Default": "ELBSecurityPolicy-2016-08",
+            "Description": "The HTTPS SSL Security Policy Name"
+        },
+        "LoadBalancerSchemeParameter": {
+            "Type": "String",
+            "Default": "internet-facing",
+            "Description": "The Load Balancer scheme - 'internal' or 'internet-facing'"
+        },
+        "LoadBalancerSecurityGroupParameter": {
+            "Type": "List\u003cAWS::EC2::SecurityGroup::Id\u003e",
+            "Description": "The security group ID for the Load Balancer"
+        },
+        "LoadBalancerSubnetsParameter": {
+            "Type": "List\u003cAWS::EC2::Subnet::Id\u003e",
+            "Description": "The list of subnets IDs for the Load Balancer"
+        },
+        "TargetGroupHealthCheckIntervalParameter": {
+            "Type": "Number",
+            "Default": "10",
+            "Description": "The healthcheck interval"
+        },
+        "TargetGroupHealthCheckPathParameter": {
+            "Type": "String",
+            "Default": "/kube-system/healthz",
+            "Description": "The healthcheck path"
+        },
+        "TargetGroupHealthCheckPortParameter": {
+            "Type": "Number",
+            "Default": "9999",
+            "Description": "The healthcheck port"
+        },
+        "TargetGroupHealthCheckTimeoutParameter": {
+            "Type": "Number",
+            "Default": "5",
+            "Description": "The healthcheck timeout"
+        },
+        "TargetGroupTargetPortParameter": {
+            "Type": "Number",
+            "Default": "9999",
+            "Description": "The target port"
+        },
+        "TargetGroupVPCIDParameter": {
+            "Type": "AWS::EC2::VPC::Id",
+            "Description": "The VPCID for the TargetGroup"
+        },
+        "Type": {
+            "Type": "String",
+            "Default": "application",
+            "Description": "Loadbalancer Type, 'application' or 'network'"
+        }
+    },
+    "Resources": {
+        "HTTPListener": {
+            "Type": "AWS::ElasticLoadBalancingV2::Listener",
+            "Properties": {
+                "DefaultActions": [
+                    {
+                        "TargetGroupArn": {
+                            "Ref": "TG"
+                        },
+                        "Type": "forward"
+                    }
+                ],
+                "LoadBalancerArn": {
+                    "Ref": "LB"
+                },
+                "Port": 80,
+                "Protocol": "HTTP"
+            }
+        },
+        "HTTPSListener": {
+            "Type": "AWS::ElasticLoadBalancingV2::Listener",
+            "Properties": {
+                "Certificates": [
+                    {
+                        "CertificateArn": "DUMMY"
+                    }
+                ],
+                "DefaultActions": [
+                    {
+                        "TargetGroupArn": {
+                            "Ref": "TG"
+                        },
+                        "Type": "forward"
+                    }
+                ],
+                "LoadBalancerArn": {
+                    "Ref": "LB"
+                },
+                "Port": 443,
+                "Protocol": "HTTPS",
+                "SslPolicy": {
+                    "Ref": "ListenerSslPolicyParameter"
+                }
+            }
+        },
+        "HTTPSListenerCertificatefc48082457b770e278fc0bd3d392d127869993166f76e8df57d19a0e662820ea": {
+            "Type": "AWS::ElasticLoadBalancingV2::ListenerCertificate",
+            "Properties": {
+                "Certificates": [
+                    {
+                        "CertificateArn": "DUMMY"
+                    }
+                ],
+                "ListenerArn": {
+                    "Ref": "HTTPSListener"
+                }
+            }
+        },
+        "LB": {
+            "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer",
+            "Properties": {
+                "IpAddressType": {
+                    "Ref": "IpAddressType"
+                },
+                "LoadBalancerAttributes": [
+                    {
+                        "Key": "idle_timeout.timeout_seconds",
+                        "Value": "0"
+                    },
+                    {
+                        "Key": "routing.http2.enabled",
+                        "Value": "true"
+                    },
+                    {
+                        "Key": "access_logs.s3.enabled",
+                        "Value": "false"
+                    }
+                ],
+                "Scheme": {
+                    "Ref": "LoadBalancerSchemeParameter"
+                },
+                "SecurityGroups": {
+                    "Ref": "LoadBalancerSecurityGroupParameter"
+                },
+                "Subnets": {
+                    "Ref": "LoadBalancerSubnetsParameter"
+                },
+                "Tags": [
+                    {
+                        "Key": "StackName",
+                        "Value": {
+                            "Ref": "AWS::StackName"
+                        }
+                    }
+                ],
+                "Type": {
+                    "Ref": "Type"
+                }
+            }
+        },
+        "TG": {
+            "Type": "AWS::ElasticLoadBalancingV2::TargetGroup",
+            "Properties": {
+                "HealthCheckIntervalSeconds": {
+                    "Ref": "TargetGroupHealthCheckIntervalParameter"
+                },
+                "HealthCheckPath": {
+                    "Ref": "TargetGroupHealthCheckPathParameter"
+                },
+                "HealthCheckPort": {
+                    "Ref": "TargetGroupHealthCheckPortParameter"
+                },
+                "HealthCheckProtocol": "HTTP",
+                "HealthCheckTimeoutSeconds": {
+                    "Ref": "TargetGroupHealthCheckTimeoutParameter"
+                },
+                "HealthyThresholdCount": 0,
+                "Port": {
+                    "Ref": "TargetGroupTargetPortParameter"
+                },
+                "Protocol": "HTTP",
+                "TargetGroupAttributes": [
+                    {
+                        "Key": "deregistration_delay.timeout_seconds",
+                        "Value": "0"
+                    }
+                ],
+                "UnhealthyThresholdCount": 0,
+                "VpcId": {
+                    "Ref": "TargetGroupVPCIDParameter"
+                }
+            }
+        }
+    },
+    "Outputs": {
+        "LoadBalancerDNSName": {
+            "Description": "DNS name for the LoadBalancer",
+            "Value": {
+                "Fn::GetAtt": [
+                    "LB",
+                    "DNSName"
+                ]
+            }
+        },
+        "TargetGroupARN": {
+            "Description": "The ARN of the TargetGroup",
+            "Value": {
+                "Ref": "TG"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/testdata/simple_alb/ing.yaml b/testdata/simple_alb/ing.yaml
new file mode 100644
index 0000000..6b35d8c
--- /dev/null
+++ b/testdata/simple_alb/ing.yaml
@@ -0,0 +1,16 @@
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  name: myingress
+spec:
+  rules:
+  - host: foo.bar.org
+    http:
+      paths:
+      - backend:
+          service:
+            name: foo-bar-service
+            port:
+              name: main-port
+        path: /
+        pathType: ImplementationSpecific
\ No newline at end of file
diff --git a/testdata/simple_nlb/expected.cf b/testdata/simple_nlb/expected.cf
new file mode 100644
index 0000000..ac57a01
--- /dev/null
+++ b/testdata/simple_nlb/expected.cf
@@ -0,0 +1,187 @@
+{
+    "AWSTemplateFormatVersion": "2010-09-09",
+    "Description": "Load Balancer for Kubernetes Ingress",
+    "Parameters": {
+        "HTTP2": {
+            "Type": "String",
+            "Default": "true",
+            "Description": "H2 Enabled"
+        },
+        "IpAddressType": {
+            "Type": "String",
+            "Default": "ipv4",
+            "Description": "IP Address Type, 'ipv4' or 'dualstack'"
+        },
+        "ListenerSslPolicyParameter": {
+            "Type": "String",
+            "Default": "ELBSecurityPolicy-2016-08",
+            "Description": "The HTTPS SSL Security Policy Name"
+        },
+        "LoadBalancerSchemeParameter": {
+            "Type": "String",
+            "Default": "internet-facing",
+            "Description": "The Load Balancer scheme - 'internal' or 'internet-facing'"
+        },
+        "LoadBalancerSecurityGroupParameter": {
+            "Type": "List\u003cAWS::EC2::SecurityGroup::Id\u003e",
+            "Description": "The security group ID for the Load Balancer"
+        },
+        "LoadBalancerSubnetsParameter": {
+            "Type": "List\u003cAWS::EC2::Subnet::Id\u003e",
+            "Description": "The list of subnets IDs for the Load Balancer"
+        },
+        "TargetGroupHealthCheckIntervalParameter": {
+            "Type": "Number",
+            "Default": "10",
+            "Description": "The healthcheck interval"
+        },
+        "TargetGroupHealthCheckPathParameter": {
+            "Type": "String",
+            "Default": "/kube-system/healthz",
+            "Description": "The healthcheck path"
+        },
+        "TargetGroupHealthCheckPortParameter": {
+            "Type": "Number",
+            "Default": "9999",
+            "Description": "The healthcheck port"
+        },
+        "TargetGroupHealthCheckTimeoutParameter": {
+            "Type": "Number",
+            "Default": "5",
+            "Description": "The healthcheck timeout"
+        },
+        "TargetGroupTargetPortParameter": {
+            "Type": "Number",
+            "Default": "9999",
+            "Description": "The target port"
+        },
+        "TargetGroupVPCIDParameter": {
+            "Type": "AWS::EC2::VPC::Id",
+            "Description": "The VPCID for the TargetGroup"
+        },
+        "Type": {
+            "Type": "String",
+            "Default": "application",
+            "Description": "Loadbalancer Type, 'application' or 'network'"
+        }
+    },
+    "Resources": {
+        "HTTPSListener": {
+            "Type": "AWS::ElasticLoadBalancingV2::Listener",
+            "Properties": {
+                "Certificates": [
+                    {
+                        "CertificateArn": "DUMMY"
+                    }
+                ],
+                "DefaultActions": [
+                    {
+                        "TargetGroupArn": {
+                            "Ref": "TG"
+                        },
+                        "Type": "forward"
+                    }
+                ],
+                "LoadBalancerArn": {
+                    "Ref": "LB"
+                },
+                "Port": 443,
+                "Protocol": "TLS",
+                "SslPolicy": {
+                    "Ref": "ListenerSslPolicyParameter"
+                }
+            }
+        },
+        "HTTPSListenerCertificatefc48082457b770e278fc0bd3d392d127869993166f76e8df57d19a0e662820ea": {
+            "Type": "AWS::ElasticLoadBalancingV2::ListenerCertificate",
+            "Properties": {
+                "Certificates": [
+                    {
+                        "CertificateArn": "DUMMY"
+                    }
+                ],
+                "ListenerArn": {
+                    "Ref": "HTTPSListener"
+                }
+            }
+        },
+        "LB": {
+            "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer",
+            "Properties": {
+                "IpAddressType": {
+                    "Ref": "IpAddressType"
+                },
+                "LoadBalancerAttributes": [
+                    {
+                        "Key": "access_logs.s3.enabled",
+                        "Value": "false"
+                    }
+                ],
+                "Scheme": {
+                    "Ref": "LoadBalancerSchemeParameter"
+                },
+                "Subnets": {
+                    "Ref": "LoadBalancerSubnetsParameter"
+                },
+                "Tags": [
+                    {
+                        "Key": "StackName",
+                        "Value": {
+                            "Ref": "AWS::StackName"
+                        }
+                    }
+                ],
+                "Type": {
+                    "Ref": "Type"
+                }
+            }
+        },
+        "TG": {
+            "Type": "AWS::ElasticLoadBalancingV2::TargetGroup",
+            "Properties": {
+                "HealthCheckIntervalSeconds": {
+                    "Ref": "TargetGroupHealthCheckIntervalParameter"
+                },
+                "HealthCheckPath": {
+                    "Ref": "TargetGroupHealthCheckPathParameter"
+                },
+                "HealthCheckPort": {
+                    "Ref": "TargetGroupHealthCheckPortParameter"
+                },
+                "HealthCheckProtocol": "HTTP",
+                "HealthyThresholdCount": 0,
+                "Port": {
+                    "Ref": "TargetGroupTargetPortParameter"
+                },
+                "Protocol": "TCP",
+                "TargetGroupAttributes": [
+                    {
+                        "Key": "deregistration_delay.timeout_seconds",
+                        "Value": "0"
+                    }
+                ],
+                "UnhealthyThresholdCount": 0,
+                "VpcId": {
+                    "Ref": "TargetGroupVPCIDParameter"
+                }
+            }
+        }
+    },
+    "Outputs": {
+        "LoadBalancerDNSName": {
+            "Description": "DNS name for the LoadBalancer",
+            "Value": {
+                "Fn::GetAtt": [
+                    "LB",
+                    "DNSName"
+                ]
+            }
+        },
+        "TargetGroupARN": {
+            "Description": "The ARN of the TargetGroup",
+            "Value": {
+                "Ref": "TG"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/testdata/simple_nlb/ing.yaml b/testdata/simple_nlb/ing.yaml
new file mode 100644
index 0000000..21b2e45
--- /dev/null
+++ b/testdata/simple_nlb/ing.yaml
@@ -0,0 +1,18 @@
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  name: myingress
+  annotations:
+    zalando.org/aws-load-balancer-type: nlb	
+spec:
+  rules:
+  - host: foo.bar.org
+    http:
+      paths:
+      - backend:
+          service:
+            name: foo-bar-service
+            port:
+              name: main-port
+        path: /
+        pathType: ImplementationSpecific
\ No newline at end of file
diff --git a/worker_test.go b/worker_test.go
index 17022a4..9a67cfb 100644
--- a/worker_test.go
+++ b/worker_test.go
@@ -2,21 +2,344 @@ package main
 
 import (
 	"context"
+	"crypto/rand"
+	"crypto/rsa"
 	"crypto/x509"
+	"crypto/x509/pkix"
+	"fmt"
+	"math/big"
+	"net/http/httptest"
+	"os"
 	"reflect"
 	"sync"
 	"testing"
 	"time"
 
+	"github.com/aws/aws-sdk-go/service/autoscaling"
 	cloudformation "github.com/mweagle/go-cloudformation"
+	log "github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
-	"github.com/zalando-incubator/kube-ingress-aws-controller/aws"
+	awsAdapter "github.com/zalando-incubator/kube-ingress-aws-controller/aws"
 	"github.com/zalando-incubator/kube-ingress-aws-controller/certs"
 	"github.com/zalando-incubator/kube-ingress-aws-controller/kubernetes"
+	"github.com/zalando/skipper/dataclients/kubernetes/kubernetestest"
 	"k8s.io/apimachinery/pkg/util/wait"
+
+	"github.com/zalando-incubator/kube-ingress-aws-controller/aws/fake"
 )
 
+// TODO(LThiesen): This should be extracted to another file.
+// This is also just a copy of certs test so it might be useful
+// to just distribute it in a separate package like aws/fake
+type caSingleton struct {
+	once      sync.Once
+	err       error
+	chainKey  *rsa.PrivateKey
+	roots     *x509.CertPool
+	chainCert *x509.Certificate
+}
+
+type mockedCertificateProvider struct {
+	t  *testing.T
+	ca caSingleton
+}
+
+func (m *mockedCertificateProvider) GetCertificates() ([]*certs.CertificateSummary, error) {
+	tenYears := time.Hour * 24 * 365 * 10
+	altNames := []string{"foo.bar.org"}
+	arn := "DUMMY"
+	notBefore := time.Now()
+	notAfter := time.Now().Add(time.Hour * 24)
+
+	m.ca.once.Do(func() {
+		caKey, err := rsa.GenerateKey(rand.Reader, 2048)
+		if err != nil {
+			m.ca.err = fmt.Errorf("unable to generate CA key: %v", err)
+			return
+		}
+
+		caCert := x509.Certificate{
+			SerialNumber: big.NewInt(1),
+			Subject: pkix.Name{
+				Organization: []string{"Testing CA"},
+			},
+			NotBefore: time.Time{},
+			NotAfter:  time.Now().Add(tenYears),
+
+			KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
+			BasicConstraintsValid: true,
+
+			IsCA: true,
+		}
+		caBody, err := x509.CreateCertificate(rand.Reader, &caCert, &caCert, caKey.Public(), caKey)
+		if err != nil {
+			m.ca.err = fmt.Errorf("unable to generate CA certificate: %v", err)
+			return
+		}
+		caReparsed, err := x509.ParseCertificate(caBody)
+		if err != nil {
+			m.ca.err = fmt.Errorf("unable to parse CA certificate: %v", err)
+			return
+		}
+		m.ca.roots = x509.NewCertPool()
+		m.ca.roots.AddCert(caReparsed)
+
+		chainKey, err := rsa.GenerateKey(rand.Reader, 2048)
+		if err != nil {
+			m.ca.err = fmt.Errorf("unable to generate sub-CA key: %v", err)
+			return
+		}
+		chainCert := x509.Certificate{
+			SerialNumber: big.NewInt(2),
+			Subject: pkix.Name{
+				Organization: []string{"Testing Sub-CA"},
+			},
+			NotBefore: time.Time{},
+			NotAfter:  time.Now().Add(tenYears),
+
+			KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
+			BasicConstraintsValid: true,
+
+			IsCA: true,
+		}
+		chainBody, err := x509.CreateCertificate(rand.Reader, &chainCert, caReparsed, chainKey.Public(), caKey)
+		if err != nil {
+			m.ca.err = fmt.Errorf("unable to generate sub-CA certificate: %v", err)
+			return
+		}
+		chainReparsed, err := x509.ParseCertificate(chainBody)
+		if err != nil {
+			m.ca.err = fmt.Errorf("unable to parse sub-CA certificate: %v", err)
+			return
+		}
+
+		m.ca.chainKey = chainKey
+		m.ca.chainCert = chainReparsed
+	})
+
+	certKey, err := rsa.GenerateKey(rand.Reader, 2048)
+	if err != nil {
+		require.NoErrorf(m.t, err, "unable to generate certificate key")
+	}
+	cert := x509.Certificate{
+		SerialNumber: big.NewInt(3),
+		DNSNames:     altNames,
+		NotBefore:    notBefore,
+		NotAfter:     notAfter,
+
+		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
+		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
+		BasicConstraintsValid: true,
+	}
+
+	body, err := x509.CreateCertificate(rand.Reader, &cert, m.ca.chainCert, certKey.Public(), m.ca.chainKey)
+	if err != nil {
+		require.NoErrorf(m.t, err, "unable to generate certificate")
+	}
+	reparsed, err := x509.ParseCertificate(body)
+	if err != nil {
+		require.NoErrorf(m.t, err, "unable to parse certificate")
+	}
+
+	c := certs.NewCertificate(arn, reparsed, []*x509.Certificate{m.ca.chainCert})
+	return []*certs.CertificateSummary{c.WithRoots(m.ca.roots)}, nil
+}
+
+func TestResourceConversion(tt *testing.T) {
+	clusterIDTagPrefix := "kubernetes.io/cluster/"
+	clusterID := "aws:123:eu-central-1:kube-1"
+	vpcID := "1"
+	securityGroupID := "42"
+	running := int64(16) // See https://github.com/aws/aws-sdk-go/blob/master/service/ec2/api.go, type InstanceState
+
+	for _, scenario := range []struct {
+		name           string
+		responsesEC2   fake.EC2Outputs
+		responsesASG   fake.ASGOutputs
+		responsesELBv2 fake.ELBv2Outputs
+		responsesCF    fake.CFOutputs
+		typeLB         string
+	}{
+		{
+			name: "simple_alb",
+			responsesEC2: fake.EC2Outputs{DescribeInstancesPages: fake.MockDescribeInstancesPagesOutput(
+				nil,
+				fake.TestInstance{
+					Id:        "i0",
+					Tags:      fake.Tags{"aws:autoscaling:groupName": "asg1", clusterIDTagPrefix + clusterID: "owned"},
+					PrivateIp: "1.2.3.3",
+					VpcId:     vpcID,
+					State:     running,
+				},
+				fake.TestInstance{
+					Id:        "i1",
+					Tags:      fake.Tags{"aws:autoscaling:groupName": "asg1", clusterIDTagPrefix + clusterID: "owned"},
+					PrivateIp: "1.2.3.4",
+					VpcId:     vpcID,
+					State:     running,
+				},
+				fake.TestInstance{
+					Id:        "i2",
+					Tags:      fake.Tags{"aws:autoscaling:groupName": "asg1", clusterIDTagPrefix + clusterID: "owned"},
+					PrivateIp: "1.2.3.5",
+					VpcId:     vpcID,
+					State:     running,
+				}),
+				DescribeSecurityGroups: fake.R(fake.MockDescribeSecurityGroupsOutput(map[string]string{"id": securityGroupID}), nil),
+				DescribeSubnets: fake.R(fake.MockDescribeSubnetsOutput(
+					fake.TestSubnet{Id: "foo1", Name: "bar1", Az: "baz1", Tags: map[string]string{"kubernetes.io/role/elb": ""}}), nil),
+				DescribeRouteTables: fake.R(fake.MockDescribeRouteTableOutput(
+					fake.TestRouteTable{SubnetID: "foo1", GatewayIds: []string{"igw-foo1"}},
+					fake.TestRouteTable{SubnetID: "mismatch", GatewayIds: []string{"igw-foo2"}, Main: true},
+				), nil),
+			},
+			responsesASG: fake.ASGOutputs{
+				DescribeAutoScalingGroups: fake.R(fake.MockDescribeAutoScalingGroupOutput(map[string]fake.ASGtags{"asg1": {
+					clusterIDTagPrefix + clusterID: "owned",
+				}}), nil),
+				DescribeLoadBalancerTargetGroups: fake.R(&autoscaling.DescribeLoadBalancerTargetGroupsOutput{
+					LoadBalancerTargetGroups: []*autoscaling.LoadBalancerTargetGroupState{},
+				}, nil),
+				AttachLoadBalancerTargetGroups: fake.R(nil, nil),
+			},
+			responsesELBv2: fake.ELBv2Outputs{
+				DescribeTargetGroups: fake.R(nil, nil),
+				DescribeTags:         fake.R(nil, nil),
+			},
+			responsesCF: fake.CFOutputs{
+				DescribeStackPages: fake.R(nil, nil),
+				DescribeStacks:     fake.R(nil, nil),
+				CreateStack:        fake.R(fake.MockCSOutput("42"), nil),
+			},
+			typeLB: awsAdapter.LoadBalancerTypeApplication,
+		},
+		{
+			name: "simple_nlb",
+			responsesEC2: fake.EC2Outputs{DescribeInstancesPages: fake.MockDescribeInstancesPagesOutput(
+				nil,
+				fake.TestInstance{
+					Id:        "i0",
+					Tags:      fake.Tags{"aws:autoscaling:groupName": "asg1", clusterIDTagPrefix + clusterID: "owned"},
+					PrivateIp: "1.2.3.3",
+					VpcId:     vpcID,
+					State:     running,
+				},
+				fake.TestInstance{
+					Id:        "i1",
+					Tags:      fake.Tags{"aws:autoscaling:groupName": "asg1", clusterIDTagPrefix + clusterID: "owned"},
+					PrivateIp: "1.2.3.4",
+					VpcId:     vpcID,
+					State:     running,
+				},
+				fake.TestInstance{
+					Id:        "i2",
+					Tags:      fake.Tags{"aws:autoscaling:groupName": "asg1", clusterIDTagPrefix + clusterID: "owned"},
+					PrivateIp: "1.2.3.5",
+					VpcId:     vpcID,
+					State:     running,
+				}),
+				DescribeSecurityGroups: fake.R(fake.MockDescribeSecurityGroupsOutput(map[string]string{"id": securityGroupID}), nil),
+				DescribeSubnets: fake.R(fake.MockDescribeSubnetsOutput(
+					fake.TestSubnet{Id: "foo1", Name: "bar1", Az: "baz1", Tags: map[string]string{"kubernetes.io/role/elb": ""}}), nil),
+				DescribeRouteTables: fake.R(fake.MockDescribeRouteTableOutput(
+					fake.TestRouteTable{SubnetID: "foo1", GatewayIds: []string{"igw-foo1"}},
+					fake.TestRouteTable{SubnetID: "mismatch", GatewayIds: []string{"igw-foo2"}, Main: true},
+				), nil),
+			},
+			responsesASG: fake.ASGOutputs{
+				DescribeAutoScalingGroups: fake.R(fake.MockDescribeAutoScalingGroupOutput(map[string]fake.ASGtags{"asg1": {
+					clusterIDTagPrefix + clusterID: "owned",
+				}}), nil),
+				DescribeLoadBalancerTargetGroups: fake.R(&autoscaling.DescribeLoadBalancerTargetGroupsOutput{
+					LoadBalancerTargetGroups: []*autoscaling.LoadBalancerTargetGroupState{},
+				}, nil),
+				AttachLoadBalancerTargetGroups: fake.R(nil, nil),
+			},
+			responsesELBv2: fake.ELBv2Outputs{
+				DescribeTargetGroups: fake.R(nil, nil),
+				DescribeTags:         fake.R(nil, nil),
+			},
+			responsesCF: fake.CFOutputs{
+				DescribeStackPages: fake.R(nil, nil),
+				DescribeStacks:     fake.R(nil, nil),
+				CreateStack:        fake.R(fake.MockCSOutput("42"), nil),
+			},
+			typeLB: awsAdapter.LoadBalancerTypeNetwork,
+		},
+	} {
+		tt.Run(scenario.name, func(t *testing.T) {
+			b, err := os.ReadFile("./testdata/" + scenario.name + "/expected.cf")
+			if err != nil {
+				t.Fatal(err)
+			}
+			expected := string(b)
+
+			clientEC2 := &fake.EC2Client{Outputs: scenario.responsesEC2}
+			clientASG := &fake.ASGClient{Outputs: scenario.responsesASG}
+			clientELBv2 := &fake.ELBv2Client{Outputs: scenario.responsesELBv2}
+			clientCF := &fake.CFClient{Outputs: scenario.responsesCF}
+
+			a := &awsAdapter.Adapter{
+				TargetCNI: &awsAdapter.TargetCNIconfig{Enabled: false},
+			}
+			a = a.WithCustomAutoScalingClient(clientASG).
+				WithCustomEc2Client(clientEC2).
+				WithCustomElbv2Client(clientELBv2).
+				WithCustomCloudFormationClient(clientCF)
+
+			a, err = a.UpdateManifest(clusterID, vpcID)
+			if err != nil {
+				t.Error(err)
+			}
+
+			f, err := os.Open("./testdata/" + scenario.name + "/ing.yaml")
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			api, err := kubernetestest.NewAPI(kubernetestest.TestAPIOptions{}, f)
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			s := httptest.NewServer(api)
+			defer s.Close()
+
+			kubeConfig := kubernetes.InsecureConfig(s.URL)
+			ingressClassFilterList := make([]string, 0)
+
+			sslPolicy := "ELBSecurityPolicy-2016-08"
+			clusterLocalDomain := ""
+			ingressAPIVersion := kubernetes.IngressAPIVersionNetworkingV1
+
+			k, err := kubernetes.NewAdapter(
+				kubeConfig,
+				ingressAPIVersion,
+				ingressClassFilterList,
+				securityGroupID,
+				sslPolicy,
+				scenario.typeLB,
+				clusterLocalDomain,
+				true)
+			if err != nil {
+				t.Fatal(err)
+			}
+			log.SetLevel(log.DebugLevel)
+			problems := doWork(&mockedCertificateProvider{t: t}, 10, time.Hour, a, k, "")
+			if len(problems.Errors()) > 0 {
+				t.Error(problems.Errors())
+			}
+
+			assert.Equal(
+				t,
+				expected,
+				clientCF.GetLastGeneratedTemplate(),
+			)
+		})
+	}
+}
+
 func TestAddIngress(tt *testing.T) {
 	for _, test := range []struct {
 		name            string
@@ -49,17 +372,17 @@ func TestAddIngress(tt *testing.T) {
 		{
 			name: "ip address type not matching",
 			loadBalancer: &loadBalancer{
-				ipAddressType: aws.IPAddressTypeIPV4,
+				ipAddressType: awsAdapter.IPAddressTypeIPV4,
 			},
 			ingress: &kubernetes.Ingress{
-				IPAddressType: aws.IPAddressTypeDualstack,
+				IPAddressType: awsAdapter.IPAddressTypeDualstack,
 			},
 			added: false,
 		},
 		{
 			name: "don't add ingresses non-shared, non-owned load balancer",
 			loadBalancer: &loadBalancer{
-				stack: &aws.Stack{
+				stack: &awsAdapter.Stack{
 					OwnerIngress: "foo/bar",
 				},
 			},
@@ -73,7 +396,7 @@ func TestAddIngress(tt *testing.T) {
 		{
 			name: "don't add ingresses shared, to an owned load balancer",
 			loadBalancer: &loadBalancer{
-				stack: &aws.Stack{
+				stack: &awsAdapter.Stack{
 					OwnerIngress: "foo/bar",
 				},
 			},
@@ -87,7 +410,7 @@ func TestAddIngress(tt *testing.T) {
 		{
 			name: "add ingress to empty load balancer",
 			loadBalancer: &loadBalancer{
-				stack: &aws.Stack{},
+				stack: &awsAdapter.Stack{},
 				ingresses: map[string][]*kubernetes.Ingress{
 					"foo": []*kubernetes.Ingress{
 						{
@@ -106,7 +429,7 @@ func TestAddIngress(tt *testing.T) {
 		{
 			name: "fail adding when too many certs",
 			loadBalancer: &loadBalancer{
-				stack: &aws.Stack{},
+				stack: &awsAdapter.Stack{},
 				ingresses: map[string][]*kubernetes.Ingress{
 					"foo": []*kubernetes.Ingress{
 						{
@@ -173,7 +496,7 @@ func TestAddIngress(tt *testing.T) {
 			name: "Adding/changing WAF, SG or TLS settings on non-shared LB should work",
 			loadBalancer: &loadBalancer{
 				ingresses: make(map[string][]*kubernetes.Ingress),
-				stack: &aws.Stack{
+				stack: &awsAdapter.Stack{
 					OwnerIngress: "foo/bar",
 				},
 				sslPolicy: "ELBSecurityPolicy-2016-08",
@@ -204,36 +527,36 @@ func TestSortStacks(tt *testing.T) {
 
 	for _, test := range []struct {
 		name           string
-		stacks         []*aws.Stack
-		expectedStacks []*aws.Stack
+		stacks         []*awsAdapter.Stack
+		expectedStacks []*awsAdapter.Stack
 	}{
 		{
 			name:           "no stacks",
-			stacks:         []*aws.Stack{},
-			expectedStacks: []*aws.Stack{},
+			stacks:         []*awsAdapter.Stack{},
+			expectedStacks: []*awsAdapter.Stack{},
 		},
 		{
 			name: "two unsorted stacks",
-			stacks: []*aws.Stack{
-				&aws.Stack{
+			stacks: []*awsAdapter.Stack{
+				&awsAdapter.Stack{
 					Name:            "foo",
 					CertificateARNs: map[string]time.Time{},
 				},
-				&aws.Stack{
+				&awsAdapter.Stack{
 					Name: "bar",
 					CertificateARNs: map[string]time.Time{
 						"cert-arn": testTime,
 					},
 				},
 			},
-			expectedStacks: []*aws.Stack{
-				&aws.Stack{
+			expectedStacks: []*awsAdapter.Stack{
+				&awsAdapter.Stack{
 					Name: "bar",
 					CertificateARNs: map[string]time.Time{
 						"cert-arn": testTime,
 					},
 				},
-				&aws.Stack{
+				&awsAdapter.Stack{
 					Name:            "foo",
 					CertificateARNs: map[string]time.Time{},
 				},
@@ -241,28 +564,28 @@ func TestSortStacks(tt *testing.T) {
 		},
 		{
 			name: "two unsorted stacks with the same amount of certificates",
-			stacks: []*aws.Stack{
-				&aws.Stack{
+			stacks: []*awsAdapter.Stack{
+				&awsAdapter.Stack{
 					Name: "foo",
 					CertificateARNs: map[string]time.Time{
 						"different-cert-arn": testTime,
 					},
 				},
-				&aws.Stack{
+				&awsAdapter.Stack{
 					Name: "bar",
 					CertificateARNs: map[string]time.Time{
 						"cert-arn": testTime,
 					},
 				},
 			},
-			expectedStacks: []*aws.Stack{
-				&aws.Stack{
+			expectedStacks: []*awsAdapter.Stack{
+				&awsAdapter.Stack{
 					Name: "bar",
 					CertificateARNs: map[string]time.Time{
 						"cert-arn": testTime,
 					},
 				},
-				&aws.Stack{
+				&awsAdapter.Stack{
 					Name: "foo",
 					CertificateARNs: map[string]time.Time{
 						"different-cert-arn": testTime,
@@ -332,13 +655,13 @@ func TestGetAllLoadBalancers(tt *testing.T) {
 
 	for _, test := range []struct {
 		name          string
-		stacks        []*aws.Stack
+		stacks        []*awsAdapter.Stack
 		certs         []*certs.CertificateSummary
 		loadBalancers []*loadBalancer
 	}{
 		{
 			name: "one stack",
-			stacks: []*aws.Stack{
+			stacks: []*awsAdapter.Stack{
 				{
 					Scheme:        "foo",
 					SecurityGroup: "sg-123456",
@@ -358,7 +681,7 @@ func TestGetAllLoadBalancers(tt *testing.T) {
 		},
 		{
 			name: "one stack with certificates",
-			stacks: []*aws.Stack{
+			stacks: []*awsAdapter.Stack{
 				{
 					Scheme:        "foo",
 					SecurityGroup: "sg-123456",
@@ -391,7 +714,7 @@ func TestGetAllLoadBalancers(tt *testing.T) {
 		},
 		{
 			name: "non existing certificate is not added to LB",
-			stacks: []*aws.Stack{
+			stacks: []*awsAdapter.Stack{
 				{
 					Scheme:        "foo",
 					SecurityGroup: "sg-123456",
@@ -427,12 +750,12 @@ func TestGetCloudWatchAlarmConfigFromConfigMap(t *testing.T) {
 	for _, test := range []struct {
 		name     string
 		cm       *kubernetes.ConfigMap
-		expected aws.CloudWatchAlarmList
+		expected awsAdapter.CloudWatchAlarmList
 	}{
 		{
 			name:     "empty config map",
 			cm:       &kubernetes.ConfigMap{},
-			expected: aws.CloudWatchAlarmList{},
+			expected: awsAdapter.CloudWatchAlarmList{},
 		},
 		{
 			name: "config map with one data key",
@@ -441,7 +764,7 @@ func TestGetCloudWatchAlarmConfigFromConfigMap(t *testing.T) {
 					"some-key": "- AlarmName: foo\n- AlarmName: bar\n",
 				},
 			},
-			expected: aws.CloudWatchAlarmList{
+			expected: awsAdapter.CloudWatchAlarmList{
 				{AlarmName: cloudformation.String("foo")},
 				{AlarmName: cloudformation.String("bar")},
 			},
@@ -454,7 +777,7 @@ func TestGetCloudWatchAlarmConfigFromConfigMap(t *testing.T) {
 					"some-key":       "- AlarmName: foo\n- AlarmName: bar\n",
 				},
 			},
-			expected: aws.CloudWatchAlarmList{
+			expected: awsAdapter.CloudWatchAlarmList{
 				{AlarmName: cloudformation.String("foo")},
 				{AlarmName: cloudformation.String("bar")},
 				{AlarmName: cloudformation.String("baz")},
@@ -467,7 +790,7 @@ func TestGetCloudWatchAlarmConfigFromConfigMap(t *testing.T) {
 					"some-key": "{",
 				},
 			},
-			expected: aws.CloudWatchAlarmList{},
+			expected: awsAdapter.CloudWatchAlarmList{},
 		},
 		{
 			name: "config map with partially invalid yaml data",
@@ -477,7 +800,7 @@ func TestGetCloudWatchAlarmConfigFromConfigMap(t *testing.T) {
 					"some-other-key": "- AlarmName: baz\n",
 				},
 			},
-			expected: aws.CloudWatchAlarmList{
+			expected: awsAdapter.CloudWatchAlarmList{
 				{AlarmName: cloudformation.String("baz")},
 			},
 		},
@@ -499,7 +822,7 @@ func TestAttachCloudWatchAlarmsCopy(t *testing.T) {
 		lbTwo,
 	}
 
-	alarms := aws.CloudWatchAlarmList{
+	alarms := awsAdapter.CloudWatchAlarmList{
 		{AlarmName: cloudformation.String("baz")},
 	}
 
@@ -531,15 +854,15 @@ func TestIsLBInSync(t *testing.T) {
 				"bar": []*kubernetes.Ingress{{}},
 				"baz": []*kubernetes.Ingress{{}},
 			},
-			stack: &aws.Stack{
+			stack: &awsAdapter.Stack{
 				CertificateARNs: map[string]time.Time{
 					"foo": time.Time{},
 					"bar": time.Time{},
 				},
-				CWAlarmConfigHash: aws.CloudWatchAlarmList{{}}.Hash(),
+				CWAlarmConfigHash: awsAdapter.CloudWatchAlarmList{{}}.Hash(),
 				WAFWebACLID:       "foo-bar-baz",
 			},
-			cwAlarms:    aws.CloudWatchAlarmList{{}},
+			cwAlarms:    awsAdapter.CloudWatchAlarmList{{}},
 			wafWebACLID: "foo-bar-baz",
 		},
 	}, {
@@ -550,16 +873,16 @@ func TestIsLBInSync(t *testing.T) {
 				"bar": []*kubernetes.Ingress{{}},
 				"baz": []*kubernetes.Ingress{{}},
 			},
-			stack: &aws.Stack{
+			stack: &awsAdapter.Stack{
 				CertificateARNs: map[string]time.Time{
 					"foo": time.Time{},
 					"bar": time.Time{},
 					"baz": time.Time{},
 				},
-				CWAlarmConfigHash: aws.CloudWatchAlarmList{{}}.Hash(),
+				CWAlarmConfigHash: awsAdapter.CloudWatchAlarmList{{}}.Hash(),
 				WAFWebACLID:       "foo-bar-baz",
 			},
-			cwAlarms:    aws.CloudWatchAlarmList{{}, {}},
+			cwAlarms:    awsAdapter.CloudWatchAlarmList{{}, {}},
 			wafWebACLID: "foo-bar-baz",
 		},
 	}, {
@@ -570,16 +893,16 @@ func TestIsLBInSync(t *testing.T) {
 				"bar": []*kubernetes.Ingress{{}},
 				"baz": []*kubernetes.Ingress{{}},
 			},
-			stack: &aws.Stack{
+			stack: &awsAdapter.Stack{
 				CertificateARNs: map[string]time.Time{
 					"foo": time.Time{},
 					"bar": time.Time{},
 					"baz": time.Time{},
 				},
-				CWAlarmConfigHash: aws.CloudWatchAlarmList{{}}.Hash(),
+				CWAlarmConfigHash: awsAdapter.CloudWatchAlarmList{{}}.Hash(),
 				WAFWebACLID:       "foo-bar-baz",
 			},
-			cwAlarms:    aws.CloudWatchAlarmList{{}},
+			cwAlarms:    awsAdapter.CloudWatchAlarmList{{}},
 			wafWebACLID: "foo-bar",
 		},
 	}, {
@@ -590,16 +913,16 @@ func TestIsLBInSync(t *testing.T) {
 				"bar": []*kubernetes.Ingress{{}},
 				"baz": []*kubernetes.Ingress{{}},
 			},
-			stack: &aws.Stack{
+			stack: &awsAdapter.Stack{
 				CertificateARNs: map[string]time.Time{
 					"foo": time.Time{},
 					"bar": time.Time{},
 					"baz": time.Time{},
 				},
-				CWAlarmConfigHash: aws.CloudWatchAlarmList{{}}.Hash(),
+				CWAlarmConfigHash: awsAdapter.CloudWatchAlarmList{{}}.Hash(),
 				WAFWebACLID:       "foo-bar-baz",
 			},
-			cwAlarms:    aws.CloudWatchAlarmList{{}},
+			cwAlarms:    awsAdapter.CloudWatchAlarmList{{}},
 			wafWebACLID: "foo-bar-baz",
 		},
 		expect: true,
@@ -658,11 +981,11 @@ func TestMatchIngressesToLoadbalancers(t *testing.T) {
 				"foo.org",
 				"bar.org",
 			},
-			LoadBalancerType: aws.LoadBalancerTypeApplication,
+			LoadBalancerType: awsAdapter.LoadBalancerTypeApplication,
 			Shared:           true,
 		}},
 		lbs: []*loadBalancer{{
-			loadBalancerType: aws.LoadBalancerTypeApplication,
+			loadBalancerType: awsAdapter.LoadBalancerTypeApplication,
 			ingresses:        make(map[string][]*kubernetes.Ingress),
 		}},
 		validate: func(t *testing.T, lbs []*loadBalancer) {
@@ -682,7 +1005,7 @@ func TestMatchIngressesToLoadbalancers(t *testing.T) {
 		ingresses: []*kubernetes.Ingress{{
 			Name:             "foo-ingress",
 			CertificateARN:   "foo",
-			LoadBalancerType: aws.LoadBalancerTypeApplication,
+			LoadBalancerType: awsAdapter.LoadBalancerTypeApplication,
 			Shared:           true,
 		}},
 		validate: func(t *testing.T, lbs []*loadBalancer) {
@@ -702,7 +1025,7 @@ func TestMatchIngressesToLoadbalancers(t *testing.T) {
 		ingresses: []*kubernetes.Ingress{{
 			Name:             "foo-ingress",
 			CertificateARN:   "not-existing-arn",
-			LoadBalancerType: aws.LoadBalancerTypeApplication,
+			LoadBalancerType: awsAdapter.LoadBalancerTypeApplication,
 			Shared:           true,
 		}},
 		validate: func(t *testing.T, lbs []*loadBalancer) {
@@ -715,7 +1038,7 @@ func TestMatchIngressesToLoadbalancers(t *testing.T) {
 			Hostnames: []string{
 				"baz.org",
 			},
-			LoadBalancerType: aws.LoadBalancerTypeApplication,
+			LoadBalancerType: awsAdapter.LoadBalancerTypeApplication,
 			Shared:           true,
 		}},
 		validate: func(t *testing.T, lbs []*loadBalancer) {
@@ -725,7 +1048,7 @@ func TestMatchIngressesToLoadbalancers(t *testing.T) {
 		title: "multiple ingresses for the same LB",
 		ingresses: []*kubernetes.Ingress{{
 			Name:             "foo-ingress",
-			LoadBalancerType: aws.LoadBalancerTypeApplication,
+			LoadBalancerType: awsAdapter.LoadBalancerTypeApplication,
 			Shared:           true,
 			Hostnames: []string{
 				"foo.org",
@@ -733,7 +1056,7 @@ func TestMatchIngressesToLoadbalancers(t *testing.T) {
 			},
 		}, {
 			Name:             "bar-ingress",
-			LoadBalancerType: aws.LoadBalancerTypeApplication,
+			LoadBalancerType: awsAdapter.LoadBalancerTypeApplication,
 			Shared:           true,
 			Hostnames: []string{
 				"foo.org",
@@ -754,7 +1077,7 @@ func TestMatchIngressesToLoadbalancers(t *testing.T) {
 		title: "multiple ingresses for the same LB, with WAF ID",
 		ingresses: []*kubernetes.Ingress{{
 			Name:             "foo-ingress",
-			LoadBalancerType: aws.LoadBalancerTypeApplication,
+			LoadBalancerType: awsAdapter.LoadBalancerTypeApplication,
 			Shared:           true,
 			Hostnames: []string{
 				"foo.org",
@@ -763,7 +1086,7 @@ func TestMatchIngressesToLoadbalancers(t *testing.T) {
 			WAFWebACLID: "foo-bar-baz",
 		}, {
 			Name:             "bar-ingress",
-			LoadBalancerType: aws.LoadBalancerTypeApplication,
+			LoadBalancerType: awsAdapter.LoadBalancerTypeApplication,
 			Shared:           true,
 			Hostnames: []string{
 				"foo.org",
@@ -785,7 +1108,7 @@ func TestMatchIngressesToLoadbalancers(t *testing.T) {
 		title: "ingresses with different WAF IDs",
 		ingresses: []*kubernetes.Ingress{{
 			Name:             "foo-ingress",
-			LoadBalancerType: aws.LoadBalancerTypeApplication,
+			LoadBalancerType: awsAdapter.LoadBalancerTypeApplication,
 			Shared:           true,
 			Hostnames: []string{
 				"foo.org",
@@ -794,7 +1117,7 @@ func TestMatchIngressesToLoadbalancers(t *testing.T) {
 			WAFWebACLID: "foo-bar-baz",
 		}, {
 			Name:             "bar-ingress",
-			LoadBalancerType: aws.LoadBalancerTypeApplication,
+			LoadBalancerType: awsAdapter.LoadBalancerTypeApplication,
 			Shared:           true,
 			Hostnames: []string{
 				"foo.org",
@@ -851,15 +1174,15 @@ func TestBuildModel(t *testing.T) {
 		certs         CertificatesFinder
 		maxCertsPerLB int
 		ingresses     []*kubernetes.Ingress
-		stacks        []*aws.Stack
-		alarms        aws.CloudWatchAlarmList
+		stacks        []*awsAdapter.Stack
+		alarms        awsAdapter.CloudWatchAlarmList
 		globalWAFACL  string
 		validate      func(*testing.T, []*loadBalancer)
 	}{{
 		title: "no alarm, no waf",
 		ingresses: []*kubernetes.Ingress{{
 			Name:             "foo-ingress",
-			LoadBalancerType: aws.LoadBalancerTypeApplication,
+			LoadBalancerType: awsAdapter.LoadBalancerTypeApplication,
 			Shared:           true,
 			Hostnames: []string{
 				"foo.org",
@@ -881,14 +1204,14 @@ func TestBuildModel(t *testing.T) {
 		title: "with cloudwatch alarm",
 		ingresses: []*kubernetes.Ingress{{
 			Name:             "foo-ingress",
-			LoadBalancerType: aws.LoadBalancerTypeApplication,
+			LoadBalancerType: awsAdapter.LoadBalancerTypeApplication,
 			Shared:           true,
 			Hostnames: []string{
 				"foo.org",
 				"bar.org",
 			},
 		}},
-		alarms: aws.CloudWatchAlarmList{{}},
+		alarms: awsAdapter.CloudWatchAlarmList{{}},
 		validate: func(t *testing.T, lbs []*loadBalancer) {
 			require.Equal(t, 2, len(lbs))
 			for _, lb := range lbs {
@@ -904,7 +1227,7 @@ func TestBuildModel(t *testing.T) {
 		title: "with global WAF",
 		ingresses: []*kubernetes.Ingress{{
 			Name:             "foo-ingress",
-			LoadBalancerType: aws.LoadBalancerTypeApplication,
+			LoadBalancerType: awsAdapter.LoadBalancerTypeApplication,
 			Shared:           true,
 			Hostnames: []string{
 				"foo.org",
@@ -927,7 +1250,7 @@ func TestBuildModel(t *testing.T) {
 		title: "with ingress defined WAF",
 		ingresses: []*kubernetes.Ingress{{
 			Name:             "foo-ingress",
-			LoadBalancerType: aws.LoadBalancerTypeApplication,
+			LoadBalancerType: awsAdapter.LoadBalancerTypeApplication,
 			Shared:           true,
 			Hostnames: []string{
 				"foo.org",
@@ -950,7 +1273,7 @@ func TestBuildModel(t *testing.T) {
 		title: "with global and ingress defined WAF",
 		ingresses: []*kubernetes.Ingress{{
 			Name:             "foo-ingress",
-			LoadBalancerType: aws.LoadBalancerTypeApplication,
+			LoadBalancerType: awsAdapter.LoadBalancerTypeApplication,
 			Shared:           true,
 			Hostnames: []string{
 				"foo.org",
@@ -958,7 +1281,7 @@ func TestBuildModel(t *testing.T) {
 			},
 		}, {
 			Name:             "foo-ingress",
-			LoadBalancerType: aws.LoadBalancerTypeApplication,
+			LoadBalancerType: awsAdapter.LoadBalancerTypeApplication,
 			Shared:           true,
 			Hostnames: []string{
 				"foo.org",
@@ -1025,7 +1348,7 @@ func TestDoWorkPanicReturnsProblem(t *testing.T) {
 
 func Test_cniEventHandler(t *testing.T) {
 	t.Run("handles messages from channels and calls update functions", func(t *testing.T) {
-		targetCNIcfg := &aws.TargetCNIconfig{TargetGroupCh: make(chan []string, 10)}
+		targetCNIcfg := &awsAdapter.TargetCNIconfig{TargetGroupCh: make(chan []string, 10)}
 		targetCNIcfg.TargetGroupCh <- []string{"bar", "baz"}
 		targetCNIcfg.TargetGroupCh <- []string{"foo"} // flush
 		mutex := &sync.Mutex{}