Skip to content
On this page

Integration Testing

Base on stretchr/testify: A toolkit with common assertions and mocks that plays nicely with the standard library (github.com)

This document provides an example of integration testing in Golang using the Testify library. The code provided below demonstrates how to write integration tests for the UserService packages.

Example code

go
package services

import (
  "gitlab.finema.co/finema/golang-template/models" // Importing the models package
  "gitlab.finema.co/finema/golang-template/repo"   // Importing the repo package
  core "gitlab.finema.co/finema/idin-core"         // Importing the core package
  "gitlab.finema.co/finema/idin-core/repository"   // Importing the repository package
)

type IUserService interface { // Defining the IUserService interface
  Create(input *UserCreatePayload) (*models.User, core.IError)                                 // Method declaration for creating a user
  Update(id string, input *UserUpdatePayload) (*models.User, core.IError)                      // Method declaration for updating a user
  Find(id string) (*models.User, core.IError)                                                  // Method declaration for finding a user
  Pagination(pageOptions *core.PageOptions) (*repository.Pagination[models.User], core.IError) // Method declaration for pagination of users
  Delete(id string) core.IError                                                                // Method declaration for deleting a user
}

type userService struct {
  ctx core.IContext // Struct for userService with a context field
}

func (s userService) Create(input *UserCreatePayload) (*models.User, core.IError) {
  // Implementation for creating a user
  user := &models.User{
    BaseModel: models.NewBaseModel(),
    Email:     input.Email,
    FullName:  input.FullName,
  }

  ierr := repo.User(s.ctx).Create(user) // Calling the Create method of the repo.User repository
  if ierr != nil {
    return nil, s.ctx.NewError(ierr, ierr) // Returning an error if the Create method fails
  }

  return s.Find(user.ID) // Calling the Find method to retrieve the created user
}

func (s userService) Update(id string, input *UserUpdatePayload) (*models.User, core.IError) {
  // Implementation for updating a user
  user, ierr := s.Find(id) // Calling the Find method to retrieve the user to update
  if ierr != nil {
    return nil, s.ctx.NewError(ierr, ierr) // Returning an error if the Find method fails
  }

  user.FullName = input.FullName // Updating the full name of the user

  ierr = repo.User(s.ctx).Where("id = ?", id).Updates(user) // Calling the Updates method of the repo.User repository
  if ierr != nil {
    return nil, s.ctx.NewError(ierr, ierr) // Returning an error if the Updates method fails
  }

  return s.Find(user.ID) // Calling the Find method to retrieve the updated user
}

func (s userService) Find(id string) (*models.User, core.IError) {
  // Implementation for finding a user
  return repo.User(s.ctx).FindOne("id = ?", id) // Calling the FindOne method of the repo.User repository
}

func (s userService) Pagination(pageOptions *core.PageOptions) (*repository.Pagination[models.User], core.IError) {
  // Implementation for user pagination
  return repo.User(s.ctx, repo.UserOrderBy(pageOptions)).Pagination(pageOptions) // Calling the Pagination method of the repo.User repository
}

func (s userService) Delete(id string) core.IError {
  // Implementation for deleting a user
  _, ierr := s.Find(id) // Calling the Find method to check if the user exists
  if ierr != nil {
    return s.ctx.NewError(ierr, ierr) // Returning an error if the Find method fails
  }

  return repo.User(s.ctx).Delete("id = ?", id) // Calling the Delete method of the repo.User repository
}

func NewUserService(ctx core.IContext) IUserService {
  return &userService{ctx: ctx} // Creating a new instance of userService with the provided context
}
go
package repo

import (
  "gitlab.finema.co/finema/golang-template/models"
  core "gitlab.finema.co/finema/idin-core"
  "gitlab.finema.co/finema/idin-core/repository"
)

type UserOption func(repository.IRepository[models.User])

var User = func(c core.IContext, options ...UserOption) repository.IRepository[models.User] {
  r := repository.New[models.User](c)
  for _, opt := range options {
    opt(r)
  }

  return r
}

func UserOrderBy(pageOptions *core.PageOptions) UserOption {
  return func(c repository.IRepository[models.User]) {
    if len(pageOptions.OrderBy) == 0 {
      c.Order("created_at DESC")
    } else {
      c.Order(pageOptions.OrderBy)
    }
  }
}

UserServiceSuite struct and tests

go
package services

import (
	"github.com/stretchr/testify/mock"
	"gitlab.finema.co/finema/golang-template/models"
	"gitlab.finema.co/finema/golang-template/repo"
	"gitlab.finema.co/finema/idin-core/repository"
	"path"
	"path/filepath"
	"runtime"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/suite"
	core "gitlab.finema.co/finema/idin-core"
)

type UserServiceSuite struct {
	suite.Suite
	ctx core.IE2EContext
}

func TestUserControllerSuite(t *testing.T) {
	suite.Run(t, new(UserServiceSuite))
}

func (suite *UserServiceSuite) SetupTest() {
	// Setup test dependencies and initialize the svc
	suite.ctx = createE2EContext() // Create a mock implementation of core.IContext
}

// TestCreate is an example integration test for the Create method.
func (suite *UserServiceSuite) TestCreate() {
	// Test implementation
}

// TestUpdate is an example integration test for the Update method.
func (suite *UserServiceSuite) TestUpdate() {
	// Test implementation
}

// TestFind is an example integration test for the Find method.
func (suite *UserServiceSuite) TestFind() {
	// Test implementation
}

// TestPagination is an example integration test for the Pagination method.
func (suite *UserServiceSuite) TestPagination() {
	// Test implementation
}

// TestDelete is an example integration test for the Delete method.
func (suite *UserServiceSuite) TestDelete() {
	// Test implementation
}

func createE2EContext() core.IE2EContext {
	// Create a mock implementation of core.IContext for testing purposes
	// Return the mock implementation
	env := core.NewENVPath(rootDir())

	return core.NewE2EContext(&core.E2EContextOptions{
		ContextOptions: &core.ContextOptions{
			ENV: env,
		},
	})
}

func rootDir() string {
	_, b, _, _ := runtime.Caller(0)
	d := path.Join(path.Dir(b))
	return filepath.Dir(d)
}

The UserServiceSuite struct is a test suite for integration testing the userService methods. It includes the SetupTest method for setting up test dependencies and initializing the userService with a mock implementation of core.IContext. The suite also includes individual test methods for each method of userService that demonstrate how to write integration tests using the suite package from testify.

Integration Test Examples

TestCreate

This integration test demonstrates how to test the Create method of the userService.

go
// TestCreate is an example integration test for the Create method.
func (suite *UserServiceSuite) TestCreate() {
	// Prepare test data
	payload := &UserCreatePayload{
		Email:    "[email protected]",
		FullName: "John Doe",
	}

	userMock := repository.NewMock[models.User]()
	userMock.On("Create", mock.Anything).Return(nil)
	userMock.On("FindOne", mock.Anything, mock.Anything).Return(&models.User{
		BaseModel: models.NewBaseModel(),
		Email:     payload.Email,
		FullName:  payload.FullName,
	}, nil)

	repo.User = func(c core.IContext, options ...repo.UserOption) repository.IRepository[models.User] {
		return userMock
	}

	svc := NewUserService(suite.ctx)

	// Set the expectations for the mock UserService
	user, ierr := svc.Create(payload)

	// Assert the result
	assert.NoError(suite.T(), ierr)
	assert.NotNil(suite.T(), user)
	assert.Equal(suite.T(), payload.Email, user.Email)
}

In this test, we prepare test data by creating a UserCreatePayload object. We also create a mock userMock of type repository.IRepository[models.User] and set expectations for its Create and FindOne methods. We override the repo.User function to return the mock repository. Then, we create an instance of the userService with the mock context and call the Create method with the test payload. Finally, we assert that the returned user is not nil and its email matches the payload email.

Full code

go
package services

import (
  "github.com/stretchr/testify/mock"
  "gitlab.finema.co/finema/golang-template/models"
  "gitlab.finema.co/finema/golang-template/repo"
  "gitlab.finema.co/finema/idin-core/repository"
  "path"
  "path/filepath"
  "runtime"
  "testing"

  "github.com/stretchr/testify/assert"
  "github.com/stretchr/testify/suite"
  core "gitlab.finema.co/finema/idin-core"
)

type UserServiceSuite struct {
  suite.Suite
  ctx core.IE2EContext
}

func TestUserControllerSuite(t *testing.T) {
  suite.Run(t, new(UserServiceSuite))
}

func (suite *UserServiceSuite) SetupTest() {
  // Setup test dependencies and initialize the svc
  suite.ctx = createE2EContext() // Create a mock implementation of core.IContext

}
func (suite *UserServiceSuite) TestCreate() {
  // Prepare test data
  payload := &UserCreatePayload{
    Email:    "[email protected]",
    FullName: "John Doe",
  }

  userMock := repository.NewMock[models.User]()
  userMock.On("Create", mock.Anything).Return(nil)
  userMock.On("FindOne", mock.Anything, mock.Anything).Return(&models.User{
    BaseModel: models.NewBaseModel(),
    Email:     payload.Email,
    FullName:  payload.FullName,
  }, nil)

  repo.User = func(c core.IContext, options ...repo.UserOption) repository.IRepository[models.User] {
    return userMock
  }

  svc := NewUserService(suite.ctx)

  // Set the expectations for the mock UserService
  user, ierr := svc.Create(payload)

  // Assert the result
  assert.NoError(suite.T(), ierr)
  assert.NotNil(suite.T(), user)
  assert.Equal(suite.T(), payload.Email, user.Email)
}

func (suite *UserServiceSuite) TestUpdate() {
  // Prepare test data
  baseModel := models.NewBaseModel()

  id := baseModel.ID
  payload := &UserUpdatePayload{
    FullName: "John Smith",
  }

  userMock := repository.NewMock[models.User]()
  userMock.On("FindOne", "id = ?", id).Return(&models.User{
    BaseModel: baseModel,
    Email:     "[email protected]",
    FullName:  "John Doe",
  }, nil)
  userMock.On("Where", "id = ?", id).Return(nil)
  userMock.On("Updates", mock.Anything).Return(nil)
  userMock.On("FindOne", "id = ?", id).Return(&models.User{
    BaseModel: baseModel,
    Email:     "[email protected]",
    FullName:  payload.FullName,
  }, nil)

  repo.User = func(c core.IContext, options ...repo.UserOption) repository.IRepository[models.User] {
    return userMock
  }

  svc := NewUserService(suite.ctx)

  // Set the expectations for the mock UserService
  user, ierr := svc.Update(id, payload)

  // Assert the result
  assert.NoError(suite.T(), ierr)
  assert.NotNil(suite.T(), user)
  assert.Equal(suite.T(), payload.FullName, user.FullName)
}

func (suite *UserServiceSuite) TestFind() {
  // Prepare test data\
  baseModel := models.NewBaseModel()
  id := baseModel.ID

  userMock := repository.NewMock[models.User]()
  userMock.On("FindOne", "id = ?", id).Return(&models.User{
    BaseModel: baseModel,
    Email:     "[email protected]",
    FullName:  "John Doe",
  }, nil)

  repo.User = func(c core.IContext, options ...repo.UserOption) repository.IRepository[models.User] {
    return userMock
  }

  svc := NewUserService(suite.ctx)

  // Set the expectations for the mock UserService
  user, ierr := svc.Find(id)

  // Assert the result
  assert.NoError(suite.T(), ierr)
  assert.NotNil(suite.T(), user)
  assert.Equal(suite.T(), "[email protected]", user.Email)
  assert.Equal(suite.T(), "John Doe", user.FullName)
}

func (suite *UserServiceSuite) TestPagination() {
  // Prepare test data
  pageOptions := &core.PageOptions{
    Page:  1,
    Limit: 10,
  }

  paginationMock := &repository.Pagination[models.User]{
    Limit: pageOptions.Limit,
    Page:  pageOptions.Page,
    Total: 2,
    Count: 2,
    Items: []models.User{
      {BaseModel: models.NewBaseModel(), Email: "[email protected]", FullName: "User 1"},
      {BaseModel: models.NewBaseModel(), Email: "[email protected]", FullName: "User 2"},
    },
  }

  userMock := repository.NewMock[models.User]()
  userMock.On("Pagination", pageOptions).Return(paginationMock, nil)

  repo.User = func(c core.IContext, options ...repo.UserOption) repository.IRepository[models.User] {
    return userMock
  }

  svc := NewUserService(suite.ctx)

  // Set the expectations for the mock UserService
  pagination, ierr := svc.Pagination(pageOptions)

  // Assert the result
  assert.NoError(suite.T(), ierr)
  assert.NotNil(suite.T(), pagination)
  assert.Equal(suite.T(), pageOptions.Limit, pagination.Limit)
  assert.Equal(suite.T(), pageOptions.Page, pagination.Page)
  assert.Equal(suite.T(), paginationMock.Total, pagination.Total)
  assert.Equal(suite.T(), paginationMock.Count, pagination.Count)
  assert.Len(suite.T(), pagination.Items, len(paginationMock.Items))
  assert.Equal(suite.T(), "[email protected]", pagination.Items[0].Email)
  assert.Equal(suite.T(), "[email protected]", pagination.Items[1].Email)
}

func (suite *UserServiceSuite) TestDelete() {
  // Prepare test data
  baseModel := models.NewBaseModel()
  id := baseModel.ID

  userMock := repository.NewMock[models.User]()
  userMock.On("FindOne", "id = ?", id).Return(&models.User{
    BaseModel: baseModel,
    Email:     "[email protected]",
    FullName:  "John Doe",
  }, nil)
  userMock.On("Delete", "id = ?", id).Return(nil)

  repo.User = func(c core.IContext, options ...repo.UserOption) repository.IRepository[models.User] {
    return userMock
  }

  svc := NewUserService(suite.ctx)

  // Set the expectations for the mock UserService
  ierr := svc.Delete(id)

  // Assert the result
  assert.NoError(suite.T(), ierr)
}
func createE2EContext() core.IE2EContext {
  // Create a mock implementation of core.IContext for testing purposes
  // Return the mock implementation
  env := core.NewENVPath(rootDir())

  return core.NewE2EContext(&core.E2EContextOptions{
    ContextOptions: &core.ContextOptions{
      ENV: env,
    },
  })
}

func rootDir() string {
  _, b, _, _ := runtime.Caller(0)
  d := path.Join(path.Dir(b))
  return filepath.Dir(d)
}

The UserServiceSuite struct is a test suite for integration testing the userService methods. It includes the SetupTest method for setting up test dependencies and initializing the userService with a mock implementation of core.IContext. The suite also includes individual test methods for each method of userService that demonstrate how to write integration tests using the suite package from testify.

Conclusion

Integration testing is an essential part of the software development process to ensure that different components of an application work together correctly. The provided code examples demonstrate how to write integration tests for the methods of the userService using mock repositories and the suite package from testify. These tests help validate the behavior and interactions of the userService with its dependencies.

Maintained by Passakon Puttasuwan & Dev Core Team.