Unit tests for kubernetes controller - go

I'm trying to write a controller and I'm having a few issues writing tests.
I've used some code from the k8s HPA in my controller and I'm seeing something weird when using the testrestmapper.
basically when running this test with a breakpoint here I see the mappings are returned.
When I do the same the mappings are not returned.
What magic is happening here?
The following test fails
package main
import (
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/api/meta/testrestmapper"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"testing"
)
func TestT(t *testing.T) {
mapper := testrestmapper.TestOnlyStaticRESTMapper(legacyscheme.Scheme)
gk := schema.FromAPIVersionAndKind("apps/v1", "Deployment").GroupKind()
mapping, err := mapper.RESTMapping(gk)
assert.NoError(t, err)
assert.NotNil(t, mapping)
}

I think this is because you are missing an import of _ "k8s.io/kubernetes/pkg/apis/apps/install".
Without importing this path, there are no API groups or versions registered with the schema you are using to obtain the REST mapping.
By importing the path, the API group will be registered, allowing the call to schema.FromAPIVersionAndKind("apps/v1", "Deployment").GroupKind() to return a valid GroupKind.

Related

Gin or Gorm complain about a unique column index being a syntax error, somewhat inconsistently

I took the example from Gorm's docs of how to create a unique index, which seems to be be simply adding a ,unique to the column tag when declaring a model. But when I tried to run it, it would always output the following message in the console:
(/Users/[...]/main.go:16)
[2021-06-26 13:59:20] near "unique": syntax error
While it seemed bizarre that an example directly from their docs would fail, I tried running that code in isolation, and it indeed worked fine on its own. Then, adding on more and more code from my app, it seemed to start outputting that message once Gin-Gonic was introduced and gin.Default() was called. I don't know if this is only because Go won't output the error by default, or there is some sort of a clash going on. But either way, I have also never had Gorm actually create the unique index; syntax error or not.
The minimum reproducible code is as follows, though it behaves rather inconsistently, running without any error about 1 out of 5 times:
package main
import (
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/gin-gonic/gin"
)
type User struct {
gorm.Model
Name string `gorm:"size:40;index:idx_name,unique"`
}
func main() {
db, _ := gorm.Open("sqlite3", "test.db")
db.AutoMigrate(&User{})
r := gin.Default()
r.Run(":8082")
}
How would I go about fixing this; Both getting rid of the inconsistent error, and having the unique index actually being created?
If relevant, I'm running this on a Mac.
You took an example from the gorm.io but you didn't use the right packages imports.
See here the installation here: https://gorm.io/docs/#Install
You are using imports from v1 (http://v1.gorm.io/docs/) and coding with examples from the latest version. (http://gorm.io/docs/)
Look the import and the database drive initialization in the code below:
package main
import (
"github.com/gin-gonic/gin"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Name string `gorm:"size:40;index:idx_name,unique"`
}
func main() {
db, _ := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{})
db.AutoMigrate(&User{})
r := gin.Default()
r.Run("localhost:8082")
}

Testing Golang function containing call to sql.Open connection without a DB

So I'm just getting to grips with Golang. I'm writing an application for funsies just to understand stuff and get my head around it.
I have a whole bunch of functions that will interact with a DB where I pass in *SQL.DB for the function to use. I can test those easily enough using a mocked interface from sqlmock.
No problem there.
I'm now writing the Initialisation function for the application which will initiate the DB connection which will be attached to a struct and from there passed into utility functions.
However, I am struggling to find a way to easily test that connection without having the hassle of setting up an actual database.
So I guessing that I have probably either badly structured my app, or I've missed something, potentially pretty obvious.
So here is some example code to illustrate my predicament.
util.go
package main
import (
"log"
"database/sql"
"github.com/go-sql-driver/mysql"
)
func DoDBStuff(db *sql.DB) {
rows, err := db.Query("SELECT column1, column2 FROM example")
if err != nil {
log.Error(err)
}
// do stuff with rows
}
util_test.go
package main
import (
"testing"
"github.com/DATA-DOG/go-sqlmock"
)
func TestDoDBStuff(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("An error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
rows := sqlmock.NewRows([]string{"col1", "col2"})
rows.AddRow("val1", "val2")
rows.AddRow("val3", "val4")
mock.ExpectQuery("^SELECT column1, column2 from example$").WillReturnRows(rows)
DoDBStuff(db)
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("there were unfulfilled expectations: %s", err)
}
}
That all works fine, I can test my DB queries.
However Now I want to test Initialising the App.
package main
import (
"database/sql"
"github.com/go-sql-driver/mysql"
)
type App {
DB *sql.DB
// some other data structures
}
func (a *App) InitApp(connectionString string) {
a.DB = sql.Open("mysql", connectionString)
// other init stuff
}
But as I can't pass in the SQL I don't think it can be mocked, certainly not easily. So I'm struggling a bit on how to move forward.
I am intending for this to sit behind a rest API, so on startup, the app will need to initialize before being able to process any requests.
Ideally, I'd like to be able to test the REST interface without having to set up a database, delay testing with real data until I can feed the code into a Dev environment.
So really I want to know:
Is what I'm intending possible?
Is there a better approach?
If not what am I missing? Poor test design or poor code set up?
Edit:
Folling #peter's comment I just want to clarify.
I want to test the functionality of the InitDB() function but with the sql.Open call I would need to have a Database for it to connect to, If I don't then I the call would fail and I could not effectively test the function.
There is Dockertest which creates a Docker container running whatever you want (i.e. MySQL), and you can test against that. It's designed for being able to do proper integration testing, so it should be able to do what you want (you wont need sqlmock anymore too).

Can not run tests from non-root folder

I have a tests which looks like:
package tst
import (
"testing"
"github.com/demas/cowl-go/pkg/postgres"
"log"
"os"
"fmt"
"github.com/jmoiron/sqlx"
"github.com/demas/cowl-go/pkg/quzx-crawler"
"github.com/SlyMarbo/rss"
"time"
_ "github.com/lib/pq"
)
func TestMain(m *testing.M) {
prepare()
retCode := m.Run()
os.Exit(retCode)
}
func prepare() {
connectionString := fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s sslmode=disable",
os.Getenv("DBUSER"),
os.Getenv("DBPASS"),
os.Getenv("DBHOST"),
os.Getenv("DBPORT"),
os.Getenv("DBNAME"))
db, err := sqlx.Open("postgres", connectionString)
if err != nil {
log.Fatal(err)
}
db.Exec(`DELETE FROM Settings`)
db.Exec(`DELETE FROM HackerNews`)
// ....
}
Tests works fine if I keep in the root project folder, but if I move them to tst folder I get error message:
D:\development\gopath\src\github.com\demas\cowl-go\tst>go test -v
2017/03/31 16:30:06 sql: unknown driver "postgres" (forgotten import?)
exit status 1
FAIL github.com/demas/cowl-go/tst 0.085s
Why ?
As already mentioned by #JimB in the comments, the error means that you're trying to open a db connection, using sqlx.Open, without first importing a db driver. This can be fixed by, in your case, adding this _ "github.com/lib/pq" import spec.
If, even after adding that import, you're still seeing the same error, then that means that one of your dependencies is also trying to open a db connection without first importing the necessary driver.
Please note that while log.Fatal is a nice and clean way to stop your program it can sometimes be lacking, as you already know. You might want to consider using panic instead, its output is much more chaotic but, on the other hand, you'll get the line number and file name that caused the panic and eventually you'll learn to parse it quickly.

How to import a method from an external package in Golang?

Sorry if this question is a bit basic however I have not been able to find any documentation on it.
I am trying to import the following method from example.com/User/project/controllers package
func (env *Env) Index(ctx *fasthttp.RequestCtx, ps fasthttprouter.Params){
fmt.Fprintf(ctx, "Hi there! RequestURI is %q", ctx.RequestURI())
}
Into the following file to be used in a router as follows
db, err := db.Conn()
if err != nil {
log.Panic(err)
}
env := &Env{db}
...
router.GET("/", env.controllers.Index)///this import is not valid
I have tried to use controllers.env.Index env.controllers.Index I have also tried importing with a . before the import .etc
How would one in this instance import a method from another package whereby a struct (ENV) can be passed to it? To clarify the problem here is using method on top of the package ontop of the helper e.g. method.package.helper how would I resolve the above code so that I can pass a method to a helper from an external package
Thanks
Just to clarify, you would like to pass in your db struct to your Env struct correct?
This may help if I am understanding correctly.
Since a struct is really just a collection of fields, it's dependent on the makeup of the Env struct.
What is the error that you are receiving?

Golang not able to test in same package

Having issue creating unit test of one of my source file ( commonutil.go )
package util
import "github.com/nu7hatch/gouuid"
// GenerateUUID Returns generated UUID sequence
func GenerateUniqueID(hostname string) (string, error) {
var result, err = uuid.NewV4()
return hostname + ":" + result.String(), err
}
For the above source, I created the test file "commonutil_test.go" ( in the same package )
package util
import "testing"
func TestCommonUtil(t *testing.T) {
t.Run("Test generate UUID", func(t *testing.T) {
var uuid, _ = GenerateUniqueID ("test")
//fmt.Printf("UUID isa %v \n", uuid)
if uuid == "" {
t.Errorf("UUID expected, but result is empty ")
}
})
However when trying executing "go test util/commonutil_test.go" it shows :
util\commonutil_test.go:8: undefined: GenerateUniqueID
FAIL command-line-arguments [build failed]
Changing to util.GenerateUniqueID in the test solve the problem, however when running coverage using Goconvey will cause build failure :
can't load package: import cycle not allowed
package rudygunawan.com/MyProject/HFLC-Go/util
imports rudygunawan.com/MyProject/HFLC-Go/util
Any idea to solve this issue? I am confused.
Go version is go1.7.1 windows/386
I've run into a similar problem, when I was trying to run a single test file.
I wanted that, as it was a kind of test driven development thing, where I wanted to run tests only for the code I was working on at the moment, and not all the x-minutes running tests.
The solution turned out to be not running tests from a file, but rather running a specific test by name (actually a regex). So in your case I guess it would be:
go test ./util -run TestCommonUtil
An alternative seems to be listing all the files needed to build your test code:
go test util/commonutil_test.go util/commonutil.go
Just realize it is a silly mistake. The package for the test should be "util_test". Putting the test in the separate package ( but still in the same folder) help solve import cycle issue, yet still allow to solve the undefined error.
The way I normally write Go unit tests is to have one (or more) ..._test.go files, in the same package as the code being tested, with one Test... function for each broad set of tests to be done.
package util
import "testing
func TestGenerateUniqueID(t *testing.T) {
var uuid1, uuid2 string
uuid1, err = GenerateUniqueID("test")
if err != nil {
t.Errorf("Expected no error, got %s", err) // Maybe Fatalf?
}
if uuid1 == "" {
t.Errorf("Expected non-empty string, got empty string (uuid1)")
}
uuid2, err = GenerateUniqueID("test")
if err != nil {
t.Errorf("Expected no error, got %s", err) // Maybe Fatalf?
}
if uuid2 == "" {
t.Errorf("Expected non-empty string, got empty string (uuid2)")
}
if uuid1 == uuid2 {
t.Errorf("Expected uuid1 and uuid2 to be different, both are %s", uuid1)
}
}
One of the reasons I tend towards whitebox testing (where I can do "blackbox testing" by carefully not accessing package internals) is that there's usually a whole slew of non-exported code that really should be tested as well. In this specific, small, example, there's no massive argument for one over the other, since all the functionality that can be tested is already exported.

Resources