How do you write Unit, Integration, and Performance tests for the Golang language?
Key Points on Testing in Go:
1. Types of Tests:
— Unit tests: Test individual functions/components in isolation
— Integration tests: Test interactions between components, often using real databases/services
— Performance tests: Evaluate system performance under load
— End-to-end tests: Test full system functionality
2. Unit Testing Best Practices:
— Aim for 70% code coverage
— Focus on testing business logic in the service layer
— Use mocks for external dependencies (databases, APIs, etc.)
— Utilize table-driven tests for multiple test cases
— Use testify package for assertions and mocks
3. Writing Mocks:
— Use mockery tool to auto-generate mocks from interfaces
— Leverage go mock for argument matching and expectations
— Create mocks for databases, caches, gRPC clients, etc.
4. Integration Testing:
— Use Docker to set up test environments with real databases/services
— Utilize Liquibase for database migrations and seeding test data
— Test interactions between microservices
5. Performance Testing:
— Use tools like Locust.io to simulate user load
— Evaluate response times and system behavior under concurrent load
6. Test Output and Reporting:
— Use JUnit XML format for CI/CD integration
— Generate code coverage reports with Cobertura
7. Additional Testing Approaches:
— Behavior Driven Development (BDD)
— Security scanning with tools like Trivy
— Contract testing for microservices with Pact
8. Go-Specific Testing Features:
— TestMain for custom test setup/teardown
— Use of testing.T struct for test state and assertions
— Built-in benchmarking capabilities
Key Recommendations:
- Start with unit tests for core business logic
- Gradually add integration and performance tests
- Leverage mocking to isolate components under test
- Automate tests in the CI/CD pipeline
- Monitor test coverage and expand over time
- Use specialized tools for security and load testing
Overall, Go provides a robust testing framework that can be extended with community tools to enable comprehensive testing of applications. A multi-layered testing approach combining unit, integration, and performance tests is recommended for building reliable Go applications.
Special thanks to Mohsen Mottaghi.

In all applications, the only way we can be sure every function works correctly is by writing the test. Special financial applications need a test and 70 percent of the covering code. One of the reasons we need to test is if some new development will add to our company and change financial calculations, how can we ensure that everything will work correctly?
Is it necessary to write a test, or is it a waste of time?
Developer: “When is the project expected to be completed?”
Business Owner: “Yesterday”
The powerful advantage of writing the test is maintenance and being ready for critical situations before they happen. If the company doesn’t write tests, the client will test your application, which will have a terrible effect on your company. How can a developer test concurrent situations only by running the code on his local machine? Or test many scenarios that only happen in unusual situations?
We need to look after the stability of all of our code with preview stages.
Chat GPT Opinion about “why we need to write Test”?
Moreover, writing tests is not just a one-time affair but a repetitive one. Every code change needs a different set of test scenarios. Writing tests enables developers to automatically check for bugs or problems quickly after each small change without manually running every scenario or testing environment, which greatly reduces the cost and increases the accuracy and stability of applications.
Also, tests always help when developers need to make bigger changes to the app because they can quickly tell if buyers are still able to use applications as before with full confidence.
In brief, writing the tests saves a lot of time (by removing manual testing), gives peace of mind, lets you track down problematic areas easily, and helps you develop stable and easy-to-maintain software applications.
Where do we need to write a test?
Absolutely, we need unit tests in the Service layer. This means that we don’t need to write tests for the client or controller layer. I saw somebody write tests to connect to the database, but the main question is, “Is it our responsibility to write tests to the Database?” Absolutely not. However, in Integration Tests, we cover the controller layer, too.
Start writing tests from the core of the business and an essential part of the service. If your code doesn’t have tests yet, find a major part of your code and then write a 30% test for the codes.
Unit test
The tiny unit usually checks the service layer for TDD, and the check code is valid after other changes happen.
Although receiving 100% coverage is the dream for every company, in the real world, achieving 70% coverage will be perfect.
We need to build a docker file for the Unit test in DevOps Pipeline.
Golang testify package
Golang testify Example
Example for testing logic without Databases
package service_test
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestService_NameTest(t *testing.T) {
type Case struct {
name []string
want string
}
cases := []Case{
{
names: []string{
"Hi"
}, want: "4422a",
}, {
names: []string{
"BYE"
}, want: "5522b",
},
}
for _, currentCase := range cases {
res := Function(case.names)
assert.Equal(t, currentCase.want, res)
}
}Run test file
go test -v ./path=== RUN TestService_NameTest--- PASS: TestService_NameTest (0.00s)Test with the percentage of cover
go test -v -cover ./path=== RUN TestService_NameTest--- PASS: TestService_NameTest
(0.00s)coverage: 32.1% of statementsok ./path 0.002s coverage: 32.1% of statementsInstall and run Gotest
go install gotest.tools/gotestsum@latestgotestsum --junitfile unit-tests.xml✓ path1 (cached)✓ path2(cached)∅ app
Install and run gocover-cobertura
go get github.com/boumenot/gocover-cobertura
go install github.com/boumenot/gocover-cobertura@latestgo test -coverprofile=coverage.txt -covermode count ./pathgocover-cobertura < coverage.txt >
coverage.xmlWrite a docker file and execute the test inside of the docker file


Test unit and coverage Sample
gotestsum --format pkgname --packages="./..." --junitfile /test-results/unit-tests.xmlgo test
-coverprofile=/test-results/coverage.txt -covermode=count -coverpkg=./... -v
./...gocover-cobertura < /test-results/coverage.txt > /test-results/coverage.xml
Write Mock Object
Another excellent feature of the testify the package is its mocking capabilities. Mocking effectively allows us to write replacement objects that mock the behaviors of certain objects in our code that we don’t necessarily want to trigger every time we run our test suite.
A good example of a mock is the link below.
Auto-generated test :
// smsServiceMocktype smsServiceMock struct { mock.Mock}// Our mocked smsService methodfunc (m
*smsServiceMock) SendChargeNotification(value int) bool {
fmt.Println("Mocked charge notification function") fmt.Printf("Value passed in: %d\n", value) //
this records that the method was called and passes in the value // it was called with args :=
m.Called(value) // it then returns whatever we tell it to return // in this case true to simulate
an SMS Service Notification // sent out return args.Bool(0)}// we need to satisfy our
MessageService interface// which sadly means we have to stub out every method// defined in that
interfacefunc (m *smsServiceMock) DummyFunc() { fmt.Println("Dummy")}Our code for using the mock service :
// TestChargeCustomer is where the magic happens// here we create our SMSService mockfunc
TestChargeCustomer(t *testing.T) { smsService := new(smsServiceMock) // we then define what
should be returned from SendChargeNotification // when we pass in the value 100 to it. In this
case, we want to return // true as it was successful in sending a notification
smsService.On("SendChargeNotification", 100).Return(true) // next we want to define the service we
wish to test myService := MyService{smsService} // and call said method
myService.ChargeCustomer(100) // at the end, we verify that our myService.ChargeCustomer // method
called our mocked SendChargeNotification method smsService.AssertExpectations(t)}The command to run the test : go test ./... -v
go test ./... --failfastGenerate a Mock based on the interface
Thankfully, this is where the vektra/mockery package comes to our aide.
The mockery binary can take the name of any interfaces you may have defined within your Go packages and it’ll automatically output the generated mocks to mocks/InterfaceName.go. This is seriously handy when you want to save yourself a tonne of time and it’s a tool I would highly recommend checking out!
Add mock folder for all services.
Each service is related to the other service, so use a mock service to test only one service.
package example_project
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Foo(s Stringer) string {
return s.String()
}
func TestString(t *testing.T) {
mockStringer := NewMockStringer(t)
mockStringer.EXPECT().String().Return("mockery")
assert.Equal(t, "mockery", Foo(mockStringer))
}
Example: Write Mock a service with mockery
You can test getFromDB You can either instantiate a testing database or simply create a mock implementation DB using mockery. Mockery can autogenerate a mock implementation that allows us to define assertions on how the mock was used, what to return, and other useful tidbits. We can add a //go:generate directive above our interface:
//go:generate mockery --name NameOfInterfacetype NameOfInterface interface { Get(val string)
string}Put “.mockery.yaml” file on top of your project
inpackage: Truetestonly: Truewith-expecter: Truekeeptree: FalseRun the command “go generate” to build a mock file, and then we will have a mock file service.
If you want to use mock service, definitely need to use Dependency Injection.
mockH := mocks.NewMyInterface(t)mockH.EXPECT().MyFuction(ctx, "aaa").Return("aa", nil).Once()
Using argument matches
Sometimes, you don’t care about the specific arguments a mock is called with. With GoMock, a parameter can be expected to have a fixed value (by specifying the value in the expected call), or it can be expected to match a predicate called a Matcher. Matchers are used to represent ranges of expected arguments using a mocked method. The following matches are pre-defined in GoMock:
gomock.Any(): matches any value (of any type)gomock.Eq(x): uses reflection to match values that areDeepEqualtoxgomock.Nil(): matchesnilgomock.Not(m): (wheremis a Matcher) matches values not matched by the matchermgomock.Not(x): (wherexis not a Matcher) matches values notDeepEqualtox
https://gist.github.com/thiagozs/4276432d12c2e5b152ea15b3f8b0012estore.EXPECT().CreateUser(gomock.Any(), gomock.Any()).Times(1)Mockery example when inputs doesn't have exact value
import . "github.com/stretchr/testify/mock"proxyMock := mocks.NewProxy(t)proxyMock.On("passthrough",
mock.AnythingOfType("context.Context"), mock.AnythingOfType("string")).
Return(func(ctx context.Context, s string) string { return s
})How to mock with special condition
https://pkg.go.dev/github.com/stretchr/testify/mockfunc MatchedBy
func MatchedBy(fn interface{
}) argumentMatcherMatchedBy can be used to match a mock call based only on certain properties from a complex structure or some calculation. It takes a function that will be evaluated with the called argument and will return true when there’s a match and false otherwise.
Example: m.On("Do", MatchedBy(func(req *http.Request) bool { return req.Host == "example.com" }))|fn| must be a function accepting a single argument (of the expected type) that returns a bool. If |fn| doesn’t match the required signature, MatchedBy() panics.

Mock Database
If your code obeys the dependency injection rules, you can easily inject the database in the integration test. This work needs to separate the controller layer from the service layer, too. In another method, you write your service again with the mock service
There is no built-in mocking framework for pgx.
But here are three approaches for testing with pgx.
First, I typically integrate over the database when possible.
Second, when testing lower level functionality, the pgmock package allows mocking the database server. This is very low level and should probably not be used for application level testing, but it is possible.
pgxmock is a mock library implementing pgx — PostgreSQL Driver and Toolkit. It’s based on the well-known sqlmock library for sql/driver.
GitHub repository:
examples:
Mock Redis
A mock of Redis can easily be injected and replaced with real Redis.
GitHub repository Mini Redis:
Sometimes, you want to test code that uses Redis without making it a full-blown integration test. Miniredis implements (parts of) the Redis server, which is to be used in unit tests. It enables a cheap, in-memory Redis replacement with a real TCP interface. Think of it as the Redis version of net/http/httptest.
func mockRedis() *miniredis.Miniredis {
s, err := miniredis.Run() if err != nil {
panic(err)
}
return s
}
redisServer := mockRedis()redisClient = redis.NewClient(&redis.Options{
Addr: redisServer.Addr()
})Sample code for Mini Redis:
GitHub repository Redis mock:
GitHub example:
more explain:
Mock GRPC
It is necessary for microservices to mock GRPC to easily write tests in only one service. If I want to write a test for a process in a single micro server, then it is better to mock other services.
Go-micro Mock :
Example, use a mock in code
mockGRPC "github.com/go-micro/plugins/v4/client/mock" response := []mockGRPC.MockResponse{
{Endpoint: "Foo.Bar", Response: map[string]interface{}{"foo": "bar"}}, } clientMock :=
mockGRPC.NewClient(mockGRPC.Response("go.mock", response))Go-micro mock test:
A good example of a mock with GRPC:
go-mock sample
mockGreeterClient.EXPECT().SayHello( gomock.Any(), // expect any value for first parameter
gomock.Any(), // expect any value for second parameter).Return(&helloworld.HelloReply{Message:
“Mocked RPC”}, nil)We use Gomock to mock the client interface (in the generated code) and programmatically set its methods to expect and return pre-determined values. This enables users to write tests around the client logic and use this mocked stub while making RPC calls.
Docker file sample:
FROM golang:1.20WORKDIR /usr/src/appCOPY go.mod go.sum ./RUN go mod download && go mod verifyCOPY .
.RUN
go build -v -o /usr/local/bin/app ./...CMD ["app"]Mock Natsjs
because Natjs is built in Golang, instead of using mock use real Natsjs and install it in your docker file.
RUN
go install github.com/nats-io/nats-server/v2@latestAlso, you can execute it in a bash file.
nats-server -js -m 9090 &export NATS_URL=localhost:4222Nats Js test scenario for push back and pull back
To test this logic, we need real Nats js, and we want to compare two different methods for fetching data.

Integration test
The integration test uses a docker to execute the database, cache, check relations, and send interactive data between microservices. Likewise, in the Integration test, microservices call each other and need to send data from one microservice to another microservice. Furthermore, real databases were used instead of mock services. DevOps deploy databases in a docker file and uses Liquibase to migrate, and seeder data into the database, and then the test will execute in the docker. All unit tests and integration tests will run in all merged branches because we want to measure the improvement of code. To visualize the results, we need Junit and Cobertura, which will be explained in the next parts.
We need to build a docker file for the integration test in DevOps.
Liquibase seeder
Load data from CSV into the database. Also, We can add SQL to insert data.
<include file="seeds/test.sql" relativeToChangelogFile="true"/>Mock Blockchain
Mock blockchain is an excellent way to run blockchain efficiently on local machines and simulate its performance. Core Blockchain has a simulator part that is useful for writing tests, integration tests, or performance tests. Again, we can use a test of blockchain service to examine everything that will be fine.
backends.NewSimulatedBackendbackend := backends.NewSimulatedBackend( core.GenesisAlloc{ crypto.PubkeyToAddress(pub):
{Balance: big.NewInt(10000000000)}, }, 10000000, )coreTokenAddress, _, _, err := contracts.DeployCoreToken(auth, s.xcbClient, fakeWrappedTokenAddr,
priceFeedAddr)JUNIT
To show the result of the unit test and integration test in the CI CD pipeline, we have to run the test under Junit output.
Execute Junit file
gotestsum --junitfile unit-tests.xmlGitLab CI CD Example for running Junit
A full example of running Junit is CI CD

Install and use Juint in Golang
go install github.com/jstemmer/go-junit-report/v2@latestRun go-junit-report
go test -v 2>&1 ./... | go-junit-report -set-exit-code > report.xmlCoverage cobertura
Cobertura is a free Java tool that calculates the percentage of code accessed by tests. It can be used to identify which parts of your Java program are lacking test coverage. It is based on jcoverage.
Cobertura on golang
End to End ( Performance Test)
End to End is the black test. We need to ensure our application will work perfectly with a lot of clients with no problem. It doesn’t need to be written in the Golang language, and it can be tested with many tools.
If we want to use end-to-end test code, we have to call all API endpoints without any UI interactions.
Loadium
Smart load testing solutions for your software. Grow with an award-winning load testing tool, master the load testing methodology, and get the most out of your application.
Simulate the behavior of customers concurrently and send data from all over the world into our application.
With these tools, we can understand response time by the increase in the number of customers. When a business wants to execute huge advertisements, this is a useful test. we want to prevent bugs before huge amounts of customers use our application.
This tool only can call API that defines in the backend.It can simulate constant senario to simulate users.
Locust is an easy-to-use, scriptable, and scalable performance testing tool. You define the behavior of your users in regular Python code, instead of being constrained by a UI or domain-specific language that only pretends to be real code. This makes Locust infinitely expandable and very developer friendly.
Pact — Integration testing is done properly

Security Packages test ( dependency check)
Trivy (pronunciation) is a comprehensive and versatile security scanner. Trivy has scanners that look for security issues and targets where it can find those issues.
Targets (what Trivy can scan):
- Container Image
- Filesystem
- Git Repository (remote)
- Virtual Machine Image
- Kubernetes
- AWS

Shadow Traffic
It means that DevOps capture traffic that comes to our services and develops uses it to enhance the performance of our algorithms.
Behavior Driven Development (BDD) and Functional Testing
Subscribe to DDIntel Here.
Visit our website here: https://www.datadriveninvestor.com
Join our network here: https://datadriveninvestor.com/collaborate
Learn go with tests
Vscode Setting for setting up a test
Make .vscode directory in your project.
Put launch.json file inside the folder.
and put the JSON below inside the file.
then press Cntl + F5
{ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of
existing attributes. // For more information, visit:
https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [
{ "name": "Launch Package", "type": "go", "request": "launch",
"mode": "auto", "program": "main.go", "envFile": "${workspaceFolder}/.env"
} ]}How to edit default go test timeout in Visual Studio Code?
You can use"go.testTimeout": "300s" on settings.json file. It worked for me.
Asnc Problem
panic: Fail in goroutine after MYService_Service_Success_Test has completed
"msg”:”Batch Init succeed failed , error: closed pool”,
/ Fail marks the function as having failed but continues execution.func (c *common) Fail() {
if c.parent != nil {
c.parent.Fail()
}
c.mu.Lock() defer c.mu.Unlock() // c.done needs to be locked to synchronize checks to c.done in
parent tests.if c.done {
panic("Fail in goroutine after " + c.name + " has completed")
}
c.failed = true
}The best solution for solving this problem is to write tests in a single process instead of writing the tests in multi tests.
When Using Natsjs, it is better to set the context timeout to exit from Natsjs
https://www.sohamkamani.com/golang/context-cancellation-and-values/
Why use TestMain for testing in Go?

Go Testing with testify/suite
import (
"testing"
"github.com/stretchr/testify/suite"
)
type MySuite struct {
suite.Suite
}
func (suite *MySuite) TestSomething() {
// Test something here
}
func TestSuite(t *testing.T) {
suite.Run(t, new(MySuite))
}