Integration, Unit and e2e Testing in Golang

Martin Pasaribu
4 min readDec 14, 2023

--

In the ever-evolving world of software development, testing plays a pivotal role in ensuring the robustness and reliability of our applications. This article will take you on a journey through the testing landscape in Golang, exploring the nuances of unit, integration, and end-to-end testing.

Why Testing Matters?

Before diving into the specifics, let’s understand why testing is crucial. Tests serve as a safety net, catching bugs early in the development process, reducing the likelihood of issues in production, and providing confidence in the codebase.

Different Types of Tests

source : https://schibsted.com/blog/impact-testing-stop-waiting-tests-not-need-run/

Unit Tests

Unit testing involves evaluating individual components or functions in isolation to ensure they behave as expected

Example Code :

func Test_usecase_GetUserDetail(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

var (
repositories = mocks.NewMockRepositories(ctrl)
usecase = usecase.NewUseCase(repositories)
id = uuid.NewString()
)

t.Run("positive_GetUserDetail", func(t *testing.T) {
var users = entities.Users{
ID: id,
Name: faker.Name(),
Password: faker.Password(),
CreatedAt: time.Now(),
}

repositories.EXPECT().Detail(context.Background(), id).Return(users, nil)

detail, err := usecase.GetUserDetail(context.Background(), id)
require.NoError(t, err)
require.Equal(t, entities.NewUsersDTO(users), detail)
})

t.Run("negative_GetUserDetail_not_found", func(t *testing.T) {
repositories.EXPECT().Detail(context.Background(), id).Return(entities.Users{}, sql.ErrNoRows)

detail, err := usecase.GetUserDetail(context.Background(), id)
require.EqualError(t, sql.ErrNoRows, err.Error())
require.Equal(t, detail, dtos.Users{})
})
}

Integration Tests

Integration testing checks the collaboration between different components or systems to verify they work seamlessly together.

Example Code :

func TestSuiteRepository(t *testing.T) {
defer func() {
err := migrationDOWN(MIGRATION_PATH, db.DB)
require.NoError(t, err)
}()

suite.Run(t, &repositoryTestSuite{repo: repository.NewRepository(db)})
}

func (r *repositoryTestSuite) Test_Repo_Repositories() {
var users = entities.Users{
ID: faker.UUIDDigit(),
Name: faker.Name(),
Password: faker.Password(),
CreatedAt: time.Now(),
}

err := r.repo.Create(context.Background(), users)
r.Assert().NoError(err)

detail, err := r.repo.Detail(context.Background(), users.ID)
r.Assert().NoError(err)
r.Assert().EqualValues(detail.ID, users.ID)
}

End-To-End

End-to-End testing simulates real user scenarios, ensuring the entire application workflow behaves as expected.

Example Code

 t.Run("positive_CreateHandler", func(t *testing.T) {
var request = dtos.Users{
Name: faker.Name(),
Password: faker.Password(),
}

requestJSON, err := json.Marshal(request)
require.NoError(t, err)

uc.EXPECT().CreateUser(context.Background(), request).Return(nil)
handlers := delivery.NewHandlers(uc)
delivery.MapRoutes(echo, handlers)

doRequest := httptest.NewRequest(http.MethodPost, "/users", strings.NewReader(string(requestJSON)))
doRequest.Header.Set("Content-Type", "application/json")
doResponse := httptest.NewRecorder()

c := echo.NewContext(doRequest, doResponse)

require.NoError(t, handlers.CreateHandler(c))
require.Equal(t, doResponse.Code, http.StatusOK)
})

Code Coverage

In the world of Golang development, effective testing is paramount. This involves various types of tests, including unit, integration, and end-to-end (E2E) tests. One crucial metric that plays a key role in evaluating the reliability of your code is test coverage.

Why Test Coverage Matters?

Test coverage is a metric that quantifies the percentage of your codebase covered by tests. It reveals areas that are thoroughly tested and those that may need more attention. This metric is essential for detecting potential bugs early, boosting code confidence, and facilitating effective refactoring.

Example :

code coverage for unit, integration & e2e testing

Testing Best Practices

Testing is a crucial part of building robust and reliable software in Golang. Here are some simple yet effective best practices to keep in mind:

1. Clear and Concise Test Names

Use descriptive names for your tests. A good test name should convey what the test is checking without needing to read the code.

2. One Assertion Per Test

Keep each test focused on verifying one specific behavior. This makes it easier to understand failures and maintain the test suite.

3. Readable Tests

Write tests as if they were documentation. Use clear and expressive language in your test code, making it easy for others (or future you) to understand.

4. Independent Tests

Ensure that each test is independent and doesn’t rely on the state of other tests. This helps in isolating issues when they occur.

5. Regular Maintenance

Treat tests as first-class citizens in your codebase. Regularly review and refactor them to ensure they remain relevant and effective.

6. Use Testing Frameworks

Leverage testing frameworks like the standard testing package and assertion libraries to simplify your test code and enhance its readability.

7. Mock External Dependencies:

When dealing with external services in integration or end-to-end tests, consider using mocks to make tests faster and more predictable.

8. Run Tests in Parallel

Golang supports running tests in parallel. Take advantage of this feature to speed up your test suite.

Conclusion

In the world of Golang development, testing isn’t just a process; it’s your code’s shield against bugs and your assurance of reliability. By embracing these simple testing best practices, you’re not only fortifying your codebase but also fostering a development environment that thrives on clarity and maintainability.

As you embark on your coding journey, remember that testing is your ally, not an obstacle. Regularly revisit and refine your tests, ensuring they evolve alongside your code. And hey, if you ever find yourself curious about the nitty-gritty details of the code behind this article, feel free to explore the trenches of my GitHub repository

There, you’ll find the actual implementations and perhaps some extra insights into the fascinating world of Golang testing.

Happy coding and testing! 🚀

--

--

Martin Pasaribu
Martin Pasaribu

Written by Martin Pasaribu

Writing applications in Java and Go. I’m currently concentrated on Go, Microservices, Open Source & Distribution Systems

No responses yet