visit
Always remember if you want to do unit testing in Go then you have to use interface and avoid using concrete API or function as far as possible. So for
aws-sdk-go
, we have interfaces for example for dynamodb
we havedynamodbiface
You can see to see what is the iface name of the service. Normally, its service-nameiface.
.Now let’s start the codingFirst I will create structs which will have cluster id as an input and emr interface as an API// ClusterInput represent input which will be given to the lambda
type ClusterInput struct {
ClusterID string `json:"clusterID"`
}
// awsService represents emr interface
type awsService struct {
emr emriface.EMRAPI
}
// newAWSService returns a new instance of emr
func newAWSService() *awsService {
awsConfig := &aws.Config{Region: aws.String("us-west-2")}
sess, err := session.NewSession(awsConfig)
if err != nil {
log.Errorf("error while creating AWS session - %s", err.Error())
}
return &awsService{
emr: emr.New(sess),
}
}
Now, comes the meaty part. I will do input validation and prepare input to the
DescribeCluster
emr API method. Rest all is simple.// getClusterStatus returns current cluster status along with an error
func (svc *awsService) getClusterStatus(input ClusterInput) (string, error) {
clusterID := input.ClusterID
if clusterID == "" {
return "", errors.New("clusterID is empty")
}
describeClusterInput := &emr.DescribeClusterInput{
ClusterId: aws.String(clusterID),
}
clusterDetails, err := svc.emr.DescribeCluster(describeClusterInput)
if err != nil {
log.Errorf("DescribeCluster error - %s", err)
return "", err
}
if clusterDetails == nil {
log.Errorf("clusterID does not exist")
return "", errors.New("clusterID does not exist")
}
clusterStatus := *clusterDetails.Cluster.Status.State
return string(clusterStatus), nil
}
The important point to see is how I used
&emr
on DescribeClusterInput
. If you want to use any other AWS service then you should be doing something similar. Let's start testing nowFor testing, I will be using because it provides and functionality. Especially mock is very important. When you write unit tests then its essential that it should not call the real service. It should always call mock service.Here first I will create mock
emr
and create mock implementation of DescribeCluster
method. After that I will create setup
method// mockEMR represents mock implementation of AWS EMR service
type mockEMR struct {
emriface.EMRAPI
mock.Mock
}
// DescribeCluster is a mocked method which return the cluster status
func (m *mockEMR) DescribeCluster(input *emr.DescribeClusterInput) (*emr.DescribeClusterOutput, error) {
args := m.Called(input)
return args.Get(0).(*emr.DescribeClusterOutput), args.Error(1)
}
func setup() (*mockEMR, *awsService) {
mockEMRClient := new(mockEMR)
mockEMR := &awsService{
emr: mockEMRClient,
}
return mockEMRClient, mockEMR
}
mockEMRClient, mockEMR := setup()
mockDescribeClusterInput := &emr.DescribeClusterInput{
ClusterId: aws.String(testCase.clusterID),
}
mockDescribeClusterOutput := &emr.DescribeClusterOutput{
Cluster: &emr.Cluster{
Status: &emr.ClusterStatus{
State: aws.String(testCase.expectedClusterStatus),
},
},
}
mockEMRClient.On("DescribeCluster", mockDescribeClusterInput).Return(mockDescribeClusterOutput, testCase.emrError)
res, err := mockEMR.getClusterStatus(testCase.expectedInput)
assert.Equal(t, testCase.expectedClusterStatus, res, testCase.message)
assert.IsType(t, testCase.expectedError, err, testCase.message)