# Unit Testing
In a dependency injection framework, how to conduct unit tests involves injecting dependencies before testing. If the test content is closely related to the injected content, consider mocking the related content. One way is to manually bury the mocked Goner into the system, and another way is to replace an already buried Goner using cemetery.ReplaceBury
.
# Assume Our Written Goner is as Follows
Filename: goner.go
package test
import (
"github.com/gone-io/gone"
"github.com/gone-io/gone/goner/config"
)
const pointNameA = "example-test-point-a"
const pointNameB = "example-test-point-b"
func NewPointA() (gone.Goner, gone.GonerId) {
return &Point{}, pointNameA
}
func NewPointB() (gone.Goner, gone.GonerId) {
return &Point{X: -1, Y: -1}, pointNameB
}
type Point struct {
gone.Flag
X int `gone:"config,example.test.point.a-x"`
Y int `gone:"config,example.test.point.a-y,default=200"`
}
type Line struct {
gone.Flag
A *Point `gone:"example-test-point-a"`
B *Point `gone:"example-test-point-b"`
}
func (*Line) Say() string {
return ""
}
func NewLine() *Line {
return &Line{}
}
func Priest(cemetery gone.Cemetery) error {
cemetery.Bury(NewPointA())
cemetery.Bury(NewPointB())
cemetery.Bury(NewLine())
return config.Priest(cemetery)
}
# We Can Write a Test File as Follows:
Filename: goner_test.go
package test
import (
"github.com/gone-io/gone"
"github.com/gone-io/gone/goner/config"
"github.com/stretchr/testify/assert"
"testing"
)
func Test_Line(t *testing.T) {
t.Run("config default", func(t *testing.T) {
gone.TestAt(pointNameA, func(point *Point) {
assert.Equal(t, point.X, 1000)
assert.Equal(t, point.Y, 200)
}, config.Priest, Priest)
})
t.Run("config default", func(t *testing.T) {
gone.Test(func(line *Line) {
assert.Equal(t, line.A.Y, 200)
}, Priest)
})
t.Run("ReplaceBury", func(t *testing.T) {
gone.Test(func(line *Line) {
assert.Equal(t, line.A.X, 20)
}, Priest, func(cemetery gone.Cemetery) error {
Mock := func() gone.Goner {
return &Point{X: 20}
}
return cemetery.ReplaceBury(Mock(), pointNameA)
})
})
}
# Using gomock for Mock Testing
To decouple, we recommend using interfaces for injection; another reason for recommending interface injection is that Go provides an interface-based mock solution, allowing all dependencies to be mocked. However, mock implementations generated by mockgen
are not Goner
and thus cannot be buried or injected; for this, we provide a solution in our utility tools.
gone mock -f ${fromGoFile} -o ${outGoFile}
The gone mock
command adds the gone.Flag
to a mock object generated by mockgen
, making it a Goner that can enter the Cemetery.
Typically, add a //go:generate
comment on the interface that needs mocking, and let the generation process complete automatically with the go generate ./...
command. Here's an example:
Filename: i_point.go
package test
//go:generate sh -c "mockgen -package=mock -source=$GOFILE | gone mock -o mock/$GOFILE"
type IPoint interface {
GetX() int
GetY() int
}
The above //go:generate sh -c "mockgen -package=mock -source=$GOFILE | gone mock -o mock/$GOFILE"
command first generates a mock object for the interface using mockgen, then pipes it to gone mock
to add the gone.Flag
tag.
Note: Keep the versions of the mockgen tool and gomock package consistent; Run the following code to install the latest versions:
go get github.com/golang/mock/gomock go get github.com/golang/mock/mockgen
The gone utility tool needs to be installed; installation reference gone Utility Tools (opens new window).
Now, let's give it a try. Create an empty directory, enter it, create the i_test.go
file as mentioned, and run the command in the current
directory:
go generate ./...
You will see that after running the command, the file mock/i_point.go
is generated.
Next, create a file named origin_point.go
with the following content:
package test
import "github.com/gone-io/gone"
type originPoint struct {
gone.Flag
}
//go:gone
func NewOriginPoint() gone.Goner {
return &originPoint{}
}
func (o *originPoint) GetX() int {
return 100
}
func (o *originPoint) GetY() int {
return 200
}
Create another file named distance_calculator.go
with the following content:
package test
import (
"github.com/gone-io/gone"
"math"
)
//go:gone
func NewDistanceCalculator() gone.Goner {
return &distanceCalculator{}
}
type distanceCalculator struct {
gone.Flag
originPoint IPoint `gone:"*"`
}
func (d *distanceCalculator) CalculateDistance FromOrigin(x, y int) float64 {
originX, originY := d.originPoint.GetX(), d.originPoint.GetY()
return math.Sqrt(math.Pow(float64(x-originX), 2) + math.Pow(float64(y-originY), 2))
}
The distanceCalculator
's business is to calculate the distance from the point (x,y int)
to originPoint
, where originPoint
is injected. Now let's write the test function for CalculateDistanceFromOrigin
as follows:
package test
import (
"example/test/mock"
"github.com/golang/mock/gomock"
"github.com/gone-io/gone"
"github.com/stretchr/testify/assert"
"testing"
)
func Test_distanceCalculator_CalculateDistanceFromOrigin(t *testing.T) {
// Create a mock controller
controller := gomock.NewController(t)
defer controller.Finish()
gone.Test(func(d *distanceCalculator) {
distance := d.CalculateDistanceFromOrigin(3, 4)
assert.Equal(t, float64(5), distance)
}, func(cemetery gone.Cemetery) error {
// Create a mock object
point := mock.NewMockIPoint(controller)
point.EXPECT().GetX().Return(0)
point.EXPECT().GetY().Return(0)
// Bury the mock object in the Cemetery
cemetery.Bury(point)
// The object under test also needs to be buried in the Cemetery
cemetery.Bury(NewDistanceCalculator())
return nil
})
}