Integration Testing
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
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
}
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
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
.
// 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
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.