How to make Unit Tests in Go

7V9R...YaGh
11 Nov 2023
50


Writing unit tests is a crucial role to test code before it has been deployed. The job of unit tests is to check the correctness of an application, and they are a crucial part of the Go programming language.

Starting step is to create a file with the sufic _test.go usually it’s in the same package as the code being tested. There are a lot of practical reasons to test your code for example if you have a bulk update/create feature with GoRoutines you want to make sure it can execute properly without trying to use same UUID to one product.

import (
 "sync"
)


type UserManager struct {
 mu      sync.Mutex
 userIDs []int
}


func (um *UserManager) CreateUser() int {
 um.mu.Lock()
 defer um.mu.Unlock()

 newUserID := len(um.userIDs) + 1
 um.userIDs = append(um.userIDs, newUserID)

 return newUserID
}


func (um *UserManager) GetUserCount() int {
 um.mu.Lock()
 defer um.mu.Unlock()

 return len(um.userIDs)
}

The above file user_management.go as you can see is not a test file but in same package directory we will create user_tests.go.

import (
 "sync"
 "testing"
)

func TestConcurrentUserCreation(t *testing.T) {
 const numRoutines = 100

 userManager := &UserManager{}


 var wg sync.WaitGroup
 wg.Add(numRoutines)


 for i := 0; i < numRoutines; i++ {
  go func() {

   defer wg.Done()
   userManager.CreateUser()
  }()
 }


 wg.Wait()

 expectedUserCount := numRoutines
 actualUserCount := userManager.GetUserCount()

 if actualUserCount != expectedUserCount {
  t.Errorf("Expected total user count %d, got %d", expectedUserCount, actualUserCount)
 }
}


The CreateUser method is protected by a mutex to ensure that the user IDs are generated and appended to the list in a thread-safe manner. When working with concurrent code, it’s crucial to ensure proper synchronization to avoid data races and maintain the correctness of your application thus testing may be applicable here.

In Go, when you’re writing tests using the testing package, t *testing.T is a parameter that represents the test state and provides methods for logging and reporting test failures. The *testing.T type is passed to each test function that you define.


Another way I used unit tests is to make a custom user creation without the help of React, Vue, Svelte etc interaction or web framework with PostMan.

func FirebaseApp(ctx context.Context) (*firebase.App, error) {
 opt := option.WithCredentialsFile("your credentials key")
 app, err := firebase.NewApp(ctx, nil, opt)
 if err != nil {
  return nil, err
 }
 return app, nil
}

func GetAuthClient(ctx context.Context) (*auth.Client, error) {
 app, err := FirebaseApp(ctx)
 if err != nil {
  return nil, err
 }
 authClient, err := app.Auth(ctx)
 if err != nil {
  return nil, err
 }
 return authClient, nil
}

The above code is just to create a connection.go script to Firebase auth instance. In auth.go parent I have the user struct.

type User struct {
 ID       string
 Email    string
 Password string
 Phone    string
}
func TestLogin(t *testing.T) {
    ctx := context.Background()
    client, err := config.GetAuthClient(ctx)
    if err != nil {
       t.Fatalf("Error creating Firebase client: %v", err)
    }

    testData := User{
       Email:    "test@yahoo.com",
       Password: "testing123",
       Phone:    "+1234567890",
    }

    t.Run(testData.Email, func(t *testing.T) {
       createdUser, err := testData.createUser(ctx, client)
       if err != nil {
          t.Fatalf("Error creating user: %v", err)
       }
       t.Logf("Created User: %+v", createdUser)
       
       retrievedUser, err := testData.getUserByEmail(ctx, testData.Email, client)
       if err != nil {
          t.Fatalf("Error getting user by email: %v", err)
       }

       t.Logf("Retrieved User: %+v", retrievedUser)
    })
}

func (u User) createUser(ctx context.Context, client *auth.Client) (*auth.UserRecord, error) {
    // [START create_user_golang]
    params := (&auth.UserToCreate{}).
       Email(u.Email).
       PhoneNumber(u.Phone).
       Password(u.Password)
    user, err := client.CreateUser(ctx, params)
    if err != nil {
       log.Fatalf("Error creating user: %v\n", err)
       return nil, err
    }
    log.Printf("Successfully created user: %v\n", user)

    return user, nil
}

func (u User) getUserByEmail(ctx context.Context, email string, client *auth.Client) (*auth.UserRecord, error) {
    user, err := client.GetUserByEmail(ctx, email)
    if err != nil {
       log.Fatalf("Error getting user by email %s: %v\n", email, err)
       return nil, err
    }
    log.Printf("Successfully fetched user data: %v\n", user)
    return user, nil
}

In the TestLogin function i want to test two conditions one is for creation of new user other is to test if i can retrieve user by email. This can come in handy when using authentication services like Firebase, Auth0.
If you use Goland IDE like i do you can simply press the triangle to run your test otherwise in VSCode etc remember to use command: 
go test auth/auth_test.go

Concluding

In summary, unit testing in Go is straightforward, and it is an integral part of the development process. It ensures code reliability, aids in early bug detection, and supports maintainability. 
Combining unit tests with other testing strategies, such as integration and end-to-end testing, contributes to building robust and high-quality software systems.

Write & Read to Earn with BULB

Learn More

Enjoy this blog? Subscribe to Mozes711

1 Comment

B
No comments yet.
Most relevant comments are displayed, so some may have been filtered out.