Home > Net >  How to Test mapped Objects in golang
How to Test mapped Objects in golang

Time:11-16

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().

CodePudding user response:

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.

  • Related