# 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
	})
}