Introduction
Test driven development also known as TDD is a good software development practice followed by all over the software industry. With unit tests we can test a small pieces of our software independently what makes it "unit".
Problem
Let's assume you're making some changes to a function which was developed by someone else previously. with the developer test you might think, it is safe to deploy. But what will happen if you missed a specific test scenario? probably it will lead to a software crash if exceptions are not properly handled. panics in go's context.
Solution
If we have already developed unit tests, then we can run it to test whether we broke the code or not. It will execute a list of specific scenarios of the software and shows if some flows are broken. if there is not unit test, I highly recommend you to implement it first, then do the changes to the function.
Libraries we can use to write unit tests
I use gomock or mockery for mocking the interfaces and testify for evaluating tests
Let'a have a look how its implemented
Sms Provider
package dependency | |
//go:generate mockgen -package=dependency -destination=sms_provider_mocks.go . SmsProvider | |
type SmsProvider interface { | |
SendSMS(number string, content string) (string, error) | |
} | |
type smsProviderImpl struct { | |
} | |
func (svc *smsProviderImpl) SendSMS(number string, content string) (string, error) { | |
// send sms | |
return "message-id", nil | |
} |
Email Provider
package dependency | |
//go:generate mockgen -package=dependency -destination=email_provider_mocks.go . EmailProvider | |
type EmailProvider interface { | |
SendEmail(from, to, subject, body string) (string, error) | |
} | |
type emailProviderImpl struct{} | |
func (e *emailProviderImpl) SendEmail(from, to, subject, body string) (string, error) { | |
//send email | |
return "message-id", nil | |
} |
And suppose notification service, which validates phone numbers and email and calls sms provider and email provider to send actual email and test messages
package service | |
import ( | |
"fmt" | |
"regexp" | |
"testing-demo/dependency" | |
) | |
type NotificationService interface { | |
SendTextNotification(number string, message string) (string, error) | |
SendEmailNotification(from, to, subject, body string) (string, error) | |
} | |
type notifySvc struct { | |
sms dependency.SmsProvider | |
email dependency.EmailProvider | |
} | |
func (client *notifySvc) SendTextNotification(number string, message string) (string, error) { | |
match, err := regexp.MatchString(`^(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}$`, number) | |
if err != nil { | |
return "", err | |
} else if !match { | |
return "", fmt.Errorf("%s is not a valid phone number", number) | |
} | |
return client.sms.SendSMS(number, message) | |
} | |
func (client *notifySvc) SendEmailNotification(from, to, subject, body string) (string, error) { | |
match, err := regexp.MatchString(`^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$`, to) | |
if err != nil { | |
return "", err | |
} else if !match { | |
return "", fmt.Errorf("%s is not a valid email address", to) | |
} | |
return client.email.SendEmail(from, to, subject, body) | |
} | |
func NewNotificationClient(sms dependency.SmsProvider, email dependency.EmailProvider) NotificationService { | |
return ¬ifySvc{sms: sms, email: email} | |
} |
The unit tests of the notification service can be written as the following
package service | |
import ( | |
"testing" | |
"testing-demo/dependency" | |
"github.com/golang/mock/gomock" | |
"github.com/stretchr/testify/assert" | |
) | |
func Test_Notification_Service(t *testing.T) { | |
ctrl := gomock.NewController(t) | |
defer ctrl.Finish() | |
t.Run("SendTextNotification", func(t *testing.T) { | |
mockSmsSvc := dependency.NewMockSmsProvider(ctrl) | |
notifyClient := NewNotificationClient(mockSmsSvc, nil) | |
t.Run("with invalid phone number", func(t *testing.T) { | |
number, message := "1233452345", "message" | |
messageID, err := notifyClient.SendTextNotification(number, message) | |
assert.Empty(t, messageID) | |
assert.NotNil(t, err) | |
}) | |
t.Run("with valid phone number", func(t *testing.T) { | |
number, message := "012 234 2345", "message" | |
mockSmsSvc.EXPECT().SendSMS(number, message).Return("message-id", nil) | |
messageID, err := notifyClient.SendTextNotification(number, message) | |
assert.NotEmpty(t, messageID) | |
assert.Nil(t, err) | |
}) | |
}) | |
t.Run("SendEmailNotification", func(t *testing.T) { | |
mockEmailSvc := dependency.NewMockEmailProvider(ctrl) | |
notifyClient := NewNotificationClient(nil, mockEmailSvc) | |
t.Run("with invalid email address", func(t *testing.T) { | |
from, to, subject, body := "from", "to", "subject", "body" | |
messageID, err := notifyClient.SendEmailNotification(from, to, subject, body) | |
assert.Empty(t, messageID) | |
assert.NotNil(t, err) | |
}) | |
t.Run("with valid email address", func(t *testing.T) { | |
from, to, subject, body := "from", "johndoe@example.com", "subject", "body" | |
mockEmailSvc.EXPECT().SendEmail(from, to, subject, body).Return("message-id", nil) | |
messageID, err := notifyClient.SendEmailNotification(from, to, subject, body) | |
assert.NotEmpty(t, messageID) | |
assert.Nil(t, err) | |
}) | |
}) | |
} |
Top comments (0)