I'm having a hard time coming up with a clean pattern to inject dependencies in a REST server that allows me to write isolated unit tests. The below structure seems to work but I'm not sure if it's thread safe.
store:
package store
type InterfaceStore interface {
Connect()
Disconnect()
User() interfaceUser
}
// Wiring up
type store struct {
db *mongo.Database
}
func (s *store) Connect() {
client, err := mongo.Connect()
if err != nil {
log.Fatal(err.Error())
}
s.db = client.Database()
}
func (s *store) Disconnect() {
s.db.Client().Disconnect(context.TODO())
}
func (s *store) User() interfaceUser {
return &user{s.db}
}
// Exposed from the package to create a store instance
func GetStore() InterfaceStore {
return &store{}
}
// User related
type interfaceUser interface {
InsertOne(models.User) (string, error)
}
type user struct {
db *mongo.Database
}
func (u *user) InsertOne(user models.User) (primitive.ObjectID, error) {
collection := u.db.Collection(collectionUsers)
// persisting user in DB
}
server:
package server
type server struct{}
func (s *server) Start() {
storeInstance := store.GetStore()
storeInstance.Connect()
defer storeInstance.Disconnect()
r := gin.Default()
keys := keys.GetKeys()
routes.InitRoutes(r, storeInstance)
port := fmt.Sprintf(":%s", keys.PORT)
r.Run(port)
}
func CreateInstance() *server {
return &server{}
}
routes:
package routes
func InitRoutes(router *gin.Engine, store store.InterfaceStore) {
router.Use(middlewares.Cors)
// createSubrouter creates a Gin routerGroup with the prefix "/user"
userRoutes(createSubrouter("/user", router), store)
}
func userRoutes(router *gin.RouterGroup, store store.InterfaceStore) {
controller := controllers.GetUserController(store)
router.GET("/", controller.Get)
}
controllers:
package controllers
type userControllers struct {
UserService services.InterfaceUser
}
func (u *userControllers) Get(c *gin.Context) {
userDetails, _ := u.UserService.FetchAllInformation(bson.M{"_id": userData.(models.User).ID})
utils.RespondWithJSON(c, userDetails)
}
func GetUserController(store store.InterfaceStore) userControllers {
userService := services.GetUserService(store)
return userControllers{
UserService: &userService,
}
}
services:
package services
type InterfaceUser interface {
FetchAllInformation(bson.M) (*models.User, error)
}
type user struct {
store store.InterfaceStore
}
func (u *user) FetchAllInformation(filter bson.M) (*models.User, error) {
user, err := u.store.User().FindOne(filter)
if err != nil {
return nil, err
}
return user, nil
}
func GetUserService(store store.InterfaceStore) user {
return user{
store: store,
}
}
By using interfaces I'm able to mock the entire service when writing tests for the controller and I can mock the entire store to test the service component without hitting the DB.
I'm wondering if the store instance is safely shared across the code because the interfaces are no pointers. Does that mean a copy of the store is created every time I pass it down the tree?
The type user struct {} definition states store is anything that implements the store.InterfaceStore interface.
If you look carefully, you're implementing it with pointer receivers. That means the (instance pointed by the) receiver will be shared.
If your mock implements them over the value-type it will be copied on method call and you'll be safe, but it will also mean this mock won't be holding new state after the method calls, which I guess is not what you want.
Bottom line, it's not really about how you defined it in the struct, by value or by reference, but what the methods accept as receiver.
Related
I try to implement some unit testing in my go code and find the topic of mocking method quite difficult.
I have the following example where I hope you can help me :)
On the first layer I have the following code:
package api
import (
"fmt"
"core"
)
type createUserDTO struct {
Id string
}
func ApiMethod() {
fmt.Println("some incoming api call wit user")
incomingUserData := &createUserDTO{Id: "testId"}
mapedUser := incomingUserData.mapUser()
mapedUser.Create()
}
func (createUserDTO *createUserDTO) mapUser() core.User {
return &core.UserCore{Id: createUserDTO.Id}
}
The second layer has the following code:
package core
import (
"fmt"
)
type CoreUser struct{ Id string }
type User interface {
Create()
}
func (user CoreUser) Create() {
fmt.Println("Do Stuff")
}
My question now is, how do I test every public method in the api package without testing the core package. Especially the method Create().
Based on the comments, I put together a trivial GitHub repository to show how I usually deal with structuring projects in Go. The repository doesn't take into consideration the test part for now but it should be pretty easy to insert them with this project structure.
Let's start with the general folders' layout:
controllers
services
db
dto
models
Now, let's see the relevant files.
models/user.go
package models
import "gorm.io/gorm"
type User struct {
*gorm.Model
Id string `gorm:"primaryKey"`
}
func NewUser(id string) *User {
return &User{Id: id}
}
Simple struct definition here.
dto/user.go
package dto
import "time"
type UserDTO struct {
Id string `json:"id"`
AddedOn time.Time `json:"added_on"`
}
func NewUserDTO(id string) *UserDTO {
return &UserDTO{Id: id}
}
We enrich the model struct with a dummy AddedOn field which needs only for the sake of the demo.
db/user.go
package db
import (
"gorm.io/gorm"
"userapp/models"
)
type UserDb struct {
Conn *gorm.DB
}
type UserDbInterface interface {
SaveUser(user *models.User) error
}
func (u *UserDb) SaveUser(user *models.User) error {
if dbTrn := u.Conn.Create(user); dbTrn.Error != nil {
return dbTrn.Error
}
return nil
}
Here, we define an interface for using the User repository. In our tests, we can provide a mock instead of an instance of the UserDb struct.
services/user.go
package services
import (
"time"
"userapp/db"
"userapp/dto"
"userapp/models"
)
type UserService struct {
DB db.UserDbInterface
}
type UserServiceInterface interface {
AddUser(inputReq *dto.UserDTO) (*dto.UserDTO, error)
}
func NewUserService(db db.UserDbInterface) *UserService {
return &UserService{
DB: db,
}
}
func (u *UserService) AddUser(inputReq *dto.UserDTO) (*dto.UserDTO, error) {
// here you can write complex logic
user := models.NewUser(inputReq.Id)
// invoke db repo
if err := u.DB.SaveUser(user); err != nil {
return nil, err
}
inputReq.AddedOn = time.Now()
return inputReq, nil
}
This is the layer that bridges connections between the presentation layer and the underlying repositories.
controllers/user.go
package controllers
import (
"encoding/json"
"io"
"net/http"
"userapp/dto"
"userapp/services"
)
type UserController struct {
US services.UserServiceInterface
}
func NewUserController(userService services.UserServiceInterface) *UserController {
return &UserController{
US: userService,
}
}
func (u *UserController) Save(w http.ResponseWriter, r *http.Request) {
reqBody, err := io.ReadAll(r.Body)
if err != nil {
panic(err)
}
r.Body.Close()
var userReq dto.UserDTO
json.Unmarshal(reqBody, &userReq)
userRes, err := u.US.AddUser(&userReq)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(err)
return
}
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(userRes)
}
Here, we defined the controller that (through Dependency Injection) uses the UserService struct.
You can find everything in my repository on GitHub
Let me know if it clarifies a little bit.
Hi I've been trying to mock a gin.Context but I have not been able to make it work
I was trying what they did in this solution but it does not work with my router this is the error I have been getting
r.POST("/urls", urlRepo.CreateUrl)
cannot use urlRepo.CreateUrl (value of type func(c controllers.Icontext)) as gin.HandlerFunc value in argument to r.POSTcompilerIncompatibleAssign
This is the interface I created to later mock and the method in which I will be testing
type Icontext interface {
BindJSON(obj interface{}) error
JSON(code int, obj interface{})
AbortWithStatus(code int)
AbortWithStatusJSON(code int, jsonObj interface{})
}
func (repository *UrlRepo) CreateUrl(c Icontext) {
var url models.Url
c.BindJSON(&url)
if !validators.IsCreateJsonCorrect(url) {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Error format in Short or Full"})
return
}
err := repository.reposito.CreateUrl(repository.Db, &url)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err})
return
}
c.JSON(http.StatusOK, url)
}
Instead of
func (repository *UrlRepo) CreateUrl(c Icontext)
it was
func (repository *UrlRepo) CreateUrl(c *gin.Context)
Strictly speaking, you can't "mock" a *gin.Context in a meaningful way, because it's a struct with unexported fields and methods.
Furthermore, you can't pass to r.POST() a function whose type is not a gin.HandlerFunc, defined as func(*gin.Context). The type of your handler CreateUrl(c Icontext) simply doesn't match.
If your goal is to unit test a Gin handler, you definitely don't have to mock the *gin.Context. What you should do is to set test values into it. For that, you can simply use gin.CreateTestContext() and manually init some of it fields. More info here.
If for some other reason, your goal is to provide an alternate implementation of a functionality of *gin.Context for use inside your handler, what you can do is define your own type with your own alternative methods and embed the *gin.Context in it.
In practice:
type MyGinContext struct {
*gin.Context
}
func (m *MyGinContext) BindJSON(obj interface{}) error {
fmt.Println("my own BindJSON")
return m.Context.BindJSON(obj) // or entirely alternative implementation
}
// Using the appropriate function signature now
func (repository *UrlRepo) CreateUrl(c *gin.Context) {
myCtx := &MyGinContext{c}
var url models.Url
_ = myCtx.BindJSON(&url) // will also print "my own BindJSON"
// ...
// other gin.Context methods are promoted and available on MyGinContext
myCtx.Status(200)
}
But honestly I'm not sure why you would want to override some methods of the *gin.Context. If you want to provide different binding logic, or even different rendering, you can implement the interfaces that are already exposed by the library. For example:
Implement a binding:
c.ShouldBindWith() takes as second parameter an interface binding.Binding which you can implement:
type MyBinder struct {
}
func (m *MyBinder) Name() string {
return "foo"
}
func (m *MyBinder) Bind(*http.Request, interface{}) error {
// stuff
return nil
}
func MyHandler(c *gin.Context) {
var foo struct{/*fields*/}
c.ShouldBindWith(&foo, &MyBinder{})
}
Implement a renderer:
type MyRenderer struct {
}
type Render interface {
func (m *MyRenderer) Render(http.ResponseWriter) error {
// ...
return nil
}
func (m *MyRenderer) WriteContentType(w http.ResponseWriter) {
header := w.Header()
if val := header["Content-Type"]; len(val) == 0 {
header["Content-Type"] = "application/foo+bar"
}
}
func MyHandler(c *gin.Context) {
c.Render(200, &MyRenderer{})
}
if you are using gin-gonic as your http router, the param for your entry point should be a *gin.Context.
So, for instance, you should be replacing this:
func (repository *UrlRepo) CreateUrl(c Icontext) {
With this
func (repository *UrlRepo) CreateUrl(c *gin.Context) {
This way you should be able to use a mock gin context as a parameter for your unit test
I have a set of functions, which uses the pool of objects. This pool has been mocked. It works fine in most of the cases. But in some functions i call the methods of objects from the pool. So i need to mock this objects too.
Lets say:
// ObjectGeter is a interface that is mocked
type ObjectGeter interface {
GetObject(id int) ObjectType, error
}
// this function is under test
func SomeFunc(og ObjectGeter,id int, otherArgument SomeType) error {
// some actions with otherArgument
// and may be return an error
obj, err := og.GetObject(id)
if err !=nil {
return errors.New("GetObject error")
}
rezult, err := obj.SomeMethod()
if err !=nil {
return errors.New("One of internal errors")
}
return rezult, nil
}
Is there a way to test whole this function? I can create interface SomeMethoder which wraps the SomeMethod(), but i can't find the way how to assign it to obj inside SomeFunc without changing the signature of GetObject to GetObject(id int) SomeMethoder,error.
Currently i see the one approach - testing by a parts.
The only solution i'v found without of changing of paradigm is a wrapper. It is pretty trivial but may be some one will need it once.
Originally i have some type:
type PoolType struct {...}
func (p *PoolType)GetObject(id int) (ObjectType, error) {...}
and interface, that wraps PoolType.GetObject and that i'v mocked.
Now i have the interface:
type SomeMethoder interface {
SomeMethod() (ResultType, error)
}
to wrap object returned by PoolType.GetObject().
To produce it i have interface:
type ObjectGeter interface {
GetObject(id int) (SomeMethoder, error)
}
and type
type MyObjectGeter struct {
pool *PoolType
}
func New(pool *PoolType) *MyObjectGeter {
return &MyObjectGeter{pool: pool}
}
func (p *MyObjectGeter)GetObject(id int) (SomeMethoder, error) {
return p.pool.GetObject(id)
}
that implements it.
So:
// this function is under test
func SomeFunc(og ObjectGeter,id int, otherArgument SomeType) error {
// some actions with otherArgument
// and may be return an error
iface, err := og.GetObject(id)
if err !=nil {
return errors.New("GetObject error")
}
rezult, err := iface.SomeMethod()
if err !=nil {
return errors.New("One of internal errors")
}
return rezult, nil
}
is called by
og := New(pool)
SomeFunc(og,id,otherArgument)
in real work.
After all to test whole SomeFunc i have to:
func TestSomeFuncSuccess (t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
objectGeter := mocks.NewMockObjectGeter(controller)
someMethoder := mocks.NewMockSomeMethoder(controller)
gomock.InOrder(
args.objectGeter.EXPECT().
GetObject(correctIdCOnst).
Return(someMethoder, nil),
args.someMethoder.EXPECT().
SomeMethod().
Return(NewResultType(...),nil).
Times(args.test.times[1]),
)
result, err := SomeFunc(objectGeter,correctIdCOnst,otherArgumentConst)
// some checks
}
So, the only untested part is MyObjectGeter.GetObject that is enough for me.
I have this code and I wanna write a unit tests for update function.
how can i mock FindByUsername function ?
I try to overwrite u.FindByUsername but it's doesn't work.
also, I can write some function to give u *UserLogic and userName string as input parameters and execute u.FindByUsername() and mock this function but it's not a clean solution I need a better solution for mocking methods inside UserOperation interface.
package logic
import (
"errors"
"fmt"
)
var (
dataStore = map[string]*User{
"optic": &User{
Username: "bla",
Password: "ola",
},
}
)
//UserOperation interface
type UserOperation interface {
Update(info *User) error
FindByUsername(userName string) (*User, error)
}
//User struct
type User struct {
Username string
Password string
}
//UserLogic struct
type UserLogic struct {
UserOperation
}
//NewUser struct
func NewUser() UserOperation {
return &UserLogic{}
}
//Update method
func (u *UserLogic) Update(info *User) error {
userInfo, err := u.FindByUsername(info.Username)
if err != nil {
return err
}
fmt.Println(userInfo.Username, userInfo.Password)
fmt.Println("do some update logic !!!")
return nil
}
//FindByUsername method
func (u *UserLogic) FindByUsername(userName string) (*User, error) {
userInfo := &User{}
var exist bool
if userInfo, exist = dataStore[userName]; !exist {
return nil, errors.New("user not found")
}
return userInfo, nil
}
Update
I try to mock function with this code
func TestUpdate2(t *testing.T) {
var MockFunc = func(userName string) (*User, error) {
return &User{Username:"foo", Password:"bar"},nil
}
user := NewUser()
user.FindByUsername = MockFunc
user.Update(&User{Username:"optic", Password:"ola"})
}
You're mixing two levels of abstraction in your UserOperation interface: Update depends on FindByUsername. To make Update testable you need to inject the UserFinder functionality into your Update method. You can do this e.g. by defining a field in the UserLogic struct:
type UserOperation interface {
Update(info *User) error
}
type UserFinder func(userName string) (*User, error)
type UserLogic struct {
UserOperation
FindByUsername UserFinder
}
//NewUser struct
func NewUser() *UserLogic { // return structs, accept interfaces!
return &UserLogic{
findByUsername: FindByUsername
}
}
func (u *UserLogic) Update(info *User) error {
userInfo, err := u.findByUsername(info.Username)
if err != nil {
return err
}
fmt.Println(userInfo.Username, userInfo.Password)
fmt.Println("do some update logic !!!")
return nil
}
func FindByUsername(userName string) (*User, error) {
userInfo := &User{}
var exist bool
if userInfo, exist = dataStore[userName]; !exist {
return nil, errors.New("user not found")
}
return userInfo, nil
}
I'm really new to mocking third-party library in go, I'm mocking cloud.google.com/go/storage right now
I'm using mockery. This is my current interface:
//Client storage client
type Client interface {
Bucket(name string) BucketHandle
Buckets(ctx context.Context, projectID string) BucketIterator
}
//BucketHandle storage's BucketHandle
type BucketHandle interface {
Attrs(context.Context) (*storage.BucketAttrs, error)
Objects(context.Context, *storage.Query) ObjectIterator
}
//ObjectIterator storage's ObjectIterator
type ObjectIterator interface {
Next() (*storage.ObjectAttrs, error)
}
//BucketIterator storage's BucketIterator
type BucketIterator interface {
Next() (*storage.BucketAttrs, error)
}
and this is how I use it in my function
//Runner runner for this module
type Runner struct {
StorageClient stiface.Client
}
.... function
//get storage client
client, err := storage.NewClient(ctx)
if err != nil {
return err
}
runner := Runner{
StorageClient: client,
}
.... rest of functions
However, I got this error:
cannot use client (type *"cloud.google.com/go/storage".Client) as type stiface.Client in field value:
*"cloud.google.com/go/storage".Client does not implement stiface.Client (wrong type for Bucket method)
have Bucket(string) *"cloud.google.com/go/storage".BucketHandle
want Bucket(string) stiface.BucketHandle
What have I done wrong here? Thanks!
Edit
here's one example of the code that I want to mock. I'd like to mock on bucketIterator.Next():
//GetBuckets get list of buckets
func GetBuckets(ctx context.Context, client *storage.Client, projectName string) []checker.Resource {
//Get bucket iterator based on a project
bucketIterator := client.Buckets(ctx, projectName)
//iterate over the buckets and store bucket details
buckets := make([]checker.Resource, 0)
for bucket, done := bucketIterator.Next(); done == nil; bucket, done = bucketIterator.Next() {
buckets = append(buckets, checker.Resource{
Name: bucket.Name,
Type: "Bucket",
})
}
return buckets
}
The error message is basically saying your stiface.Client defines an interface that *storage.Client does not implement. On first glance your code looks valid however the problem lies in your interface method signatures and because they have outputs as interfaces.
Go makes a difference between the statements:
This function returns a BucketHandle
and this function returns a *storage.BucketHandle that is a BucketHandle
Try changing your interface to return the *storage.BucketHandle. You can see a more complex example of similar behaviour in the mockery S3API example where the functions return the s3 types, not their own interfaces.
After some trial and error, the way you'd use stiface is as below
If you need to mock stiface.BucketIterator, you can create a mock as
type mockBucketIterator struct {
stiface.BucketIterator
}
and mock the Next accordingly
func (m mockBucketIterator) Next() (*storage.BucketAttrs, error) {
// mocks that you need this to return
return
}
You could use the same method to mock all the way up to satiface.Client and feed the mock client to your test.
For reference, a full example in my tests:
type clientMock struct {
stiface.Client
}
type bucketMock struct {
stiface.BucketHandle
}
type objectItMock struct {
stiface.ObjectIterator
i int
next []storage.ObjectAttrs
}
func (m clientMock) Bucket(name string) stiface.BucketHandle {
return bucketMock{}
}
and then the object iterator to return mocked iterator as well
func (it *objectItMock) Next() (a *storage.ObjectAttrs, err error) {
if it.i == len(it.next) {
err = iterator.Done
return
}
a = &it.next[it.i]
it.i += 1
return
}
func (m bucketMock) Objects(ctx context.Context, q *storage.Query) (it stiface.ObjectIterator) {
it = &objectItMock{
i: 0,
next: []storage.ObjectAttrs{
{Name: "abc"},
{Name: "def"},
{Name: "ghi"},
},
}
return
}