i start learning unit test on golang, i have do a simple test than it work, but try on real case, i found some errors, this is my test code:
test code
func TestGetAllCompetency(t *testing.T) {
_, err := competencyService.GetAllCompetency()
if err != nil {
t.Error("failed to get competency")
}
}
GetAllCompetency Code
func GetAllCompetency() ([]models.Competency, error) {
data := []models.Competency{}
res := database.Conn.
Table("competency").Order("name").
Where("competency.is_delete = ?", false).
Find(&data)
err := res.Error
if err != nil {
log.Println("[Error] competencyService.GetAllData : ", err)
return []models.Competency{}, err
}
return data, nil
}
Related
I have found a lot of questions regarding gorm mocking related to the old V1 package: github.com/jinzhu/gorm. with the usage of github.com/DATA-DOG/go-sqlmock.
I didn't find much with the v2.
My simple question is:
Suppose I have this storage package code:
...
type Storage struct {
GormDB *gorm.DB
SqlDB *sql.DB
mutex sync.Mutex
ReadTimeout int
WriteTimeout int
}
func (ps *Storage) Open(settings *Settings) error {
if err := settings.Validate(); err != nil {
return err
}
ps.mutex.Lock()
defer ps.mutex.Unlock()
if ps.GormDB != nil {
return nil
}
gormDB, err := gorm.Open(postgres.New(postgres.Config{
DSN: settings.GetDSN(),
}), &gorm.Config{
SkipDefaultTransaction: true,
})
if err != nil {
return fmt.Errorf("%s: %v", DBConnectError, err)
}
ps.GormDB = gormDB
sqlDB, err := ps.GormDB.DB()
if err != nil {
return fmt.Errorf("%s: %v", DBRetrievalError, err)
}
ps.SqlDB = sqlDB
ps.SqlDB.SetMaxIdleConns(settings.MaxIdleConnections)
ps.SqlDB.SetMaxOpenConns(settings.MaxOpenConnections)
ps.ReadTimeout = settings.ReadTimeout
ps.WriteTimeout = settings.WriteTimeout
return nil
}
How can I unit-test this function with a simple check that gorm.Open received the expected config?
I don't see any other way than passing the ORM interface to this method... It would be a tough solution to write a gorm interface and mock it myself...
Can anyone please provide a simple example of mocking such a function?
P.S.
I don't want to run the docker with Postgres for this test. It is a simple unit test, not integration.
EDIT:
Suppose I just want to mock the connection to make gorm.Open to not return an error. How can I do it?
sqlmock.NewWithDSN(settings.GetDSN()) does not help
Answering my own question here.
Thanks to #flimzy for pointing me in the right direction.
To be able to test the storage open need to modify the function:
func (ps *Storage) Open(settings *Settings, postgresConfig *postgres.Config) error {
if err := settings.Validate(); err != nil {
return err
}
ps.mutex.Lock()
defer ps.mutex.Unlock()
if ps.GormDB != nil {
return nil
}
gormDB, err := gorm.Open(postgres.New(*postgresConfig), &gorm.Config{
SkipDefaultTransaction: true,
})
if err != nil {
return fmt.Errorf("%s: %v", DBConnectError, err)
}
ps.GormDB = gormDB
sqlDB, err := ps.GormDB.DB()
if err != nil {
return fmt.Errorf("%s: %v", DBRetrievalError, err)
}
ps.SqlDB = sqlDB
ps.SqlDB.SetMaxIdleConns(settings.MaxIdleConnections)
ps.SqlDB.SetMaxOpenConns(settings.MaxOpenConnections)
ps.ReadTimeout = settings.ReadTimeout
ps.WriteTimeout = settings.WriteTimeout
return nil
}
To mock gorm connection we just pass go-sqlmock connection in the postgres config of the form postgres driver:
package postgres
import (
"github.com/DATA-DOG/go-sqlmock"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Postgres Storage", func() {
...
Describe("Open", func() {
Context("when settings are valid", func() {
It("should open a DB connection and initialize storage", func() {
sqlDB, sqlMock, _ := sqlmock.New()
sqlMock.ExpectPing()
err := storage.Open(settings, postgres.Config{Conn: sqlDB})
dbStats := storage.sqlDB.Stats()
Expect(err).ShouldNot(HaveOccurred())
Expect(dbStats.MaxOpenConnections).To(Equal(settings.MaxOpenConnections))
Expect(dbStats.OpenConnections).To(Equal(1))
Expect(storage.ReadTimeout).To(Equal(settings.ReadTimeout))
Expect(storage.WriteTimeout).To(Equal(settings.WriteTimeout))
Expect(sqlMock.ExpectationsWereMet()).To(BeNil())
})
})
})
})
I have the following function:
func CreateByID(w http.ResponseWriter, r *http.Request) {
formFile, _, err := r.FormFile("audio")
if err != nil {
return
}
defer formFile.Close()
defer r.MultipartForm.RemoveAll()
tmpFile, err := os.CreateTemp("/tmp")
if err != nil {
return
}
defer os.Remove(tmpFile.Name())
io.Copy(tmpFile, formFile)
err = validateFile(tmpFile.Name())
if err != nil {
return
}
}
func validateFile(path string) error {
fs, err := os.Stat(path)
if err != nil {
return
}
if fs.Size() < allowedSize {
return fmt.Errorf("file is too small")
}
}
which is basically creating a temp file from the HTTP request and validating it before further processing. This works fine except for the unit test. When I'm testing this function - I'm getting always filesize = 0.
Below you can see the Test case:
func TestCreateByID(t *testing.T) {
pr, pw := io.Pipe()
writer := multipart.NewWriter(pw)
go func() {
defer writer.Close()
_, err := writer.CreateFormFile("audio", "/tmp/audioFile")
if err != nil {
t.Error(err)
}
generateAudioSample("/tmp/audioFile")
}()
request := httptest.NewRequest(http.MethodGet, "/{id}")
request.Header.Add("Content-Type", writer.FormDataContentType())
response := httptest.NewRecorder()
# Calling The Actual Function
CreateByID(response, request)
handler := http.NewServeMux()
handler.ServeHTTP(response, request)
if response.Code != 200 {
t.Errorf("Expected %d, received %d", 200, response.Code)
return
}
}
generateAudioSample function is working fine because I can see the file on the filesystem and its size it bigger than 0 but for some reason this function io.Copy(tmpFile, formFile) doesn't seem to handle FormFile correctly, and is creating just an empty file on the filesystem and obviously fail the test. What else should I add to the test function in order to pass the test?
I have created a function that utilizes the grpc package in golang. I don't know if it is relevant but the purpose is the communication with a GoBGP router over grpc. An example is the following function which prints all the peers (neighbors) of the router:
func (gc *Grpc) Peers(conn *grpc.ClientConn) error {
defer conn.Close()
c := pb.NewGobgpApiClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
p := pb.ListPeerRequest{}
peer, err := c.ListPeer(ctx, &p)
if err != nil {
return err
}
for {
res, err := peer.Recv()
if err != nil {
return err
}
fmt.Println(res)
}
return nil
}
Now, I want to create unit tests for the function. To do so, I used google.golang.org/grpc/test/bufconn package, and initialized the following:
type server struct {
pb.UnimplementedGobgpApiServer
}
func (s *server) ListDefinedSet(in *pb.ListDefinedSetRequest, ls pb.GobgpApi_ListDefinedSetServer) error {
return nil
}
var lis *bufconn.Listener
const bufSize = 1024 * 1024
func init() {
lis = bufconn.Listen(bufSize)
s := grpc.NewServer()
pb.RegisterGobgpApiServer(s, &server{})
go func() {
if err := s.Serve(lis); err != nil {
fmt.Println("Server failed!")
}
}()
}
func bufDialer(context.Context, string) (net.Conn, error) {
return lis.Dial()
}
This way, I can run a unit-test creating a connection as follows:
ctx := context.Background()
conn, _ := grpc.DialContext(ctx, "bufnet", grpc.WithContextDialer(bufDialer), grpc.WithInsecure())
Peers(conn)
However, the problem is that the stream seems to be always empty and thus the peer.Recv()
always returns EOF. Is there any way to populate the stream with dummy data? If you have experience, is my methodology correct?
I've been trying to write unit tests for my http handler. The code segment is as below:
func (s *Server) handleCreateTicketOption(w http.ResponseWriter, r *http.Request) {
var t ticket.Ticket
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, er.ErrInternal.Error(), http.StatusInternalServerError)
return
}
err = json.Unmarshal(body, &t)
if err != nil {
http.Error(w, er.ErrInvalidData.Error(), http.StatusBadRequest)
return
}
ticket, err := s.TicketService.CreateTicketOption(r.Context(), t)
if err != nil {
http.Error(w, er.ErrInternal.Error(), http.StatusInternalServerError)
return
}
res, err := json.Marshal(ticket)
if err != nil {
http.Error(w, er.ErrInternal.Error(), http.StatusInternalServerError)
return
}
log.Printf("%v tickets allocated with name %v\n", t.Allocation, t.Name)
s.sendResponse(w, res, http.StatusOK)
}
Actual logic that interacts with DB. This code segment is invoked by the handler as you can see in the code above. ticket, err := s.TicketService.CreateTicketOption(r.Context(), t)
func (t *TicketService) CreateTicketOption(ctx context.Context, ticket ticket.Ticket) (*ticket.Ticket, error) {
tx, err := t.db.dbPool.Begin(ctx)
if err != nil {
return nil, er.ErrInternal
}
defer tx.Rollback(ctx)
var id int
err = tx.QueryRow(ctx, `INSERT INTO ticket (name, description, allocation) VALUES ($1, $2, $3) RETURNING id`, ticket.Name, ticket.Description, ticket.Allocation).Scan(&id)
if err != nil {
return nil, er.ErrInternal
}
ticket.Id = id
return &ticket, tx.Commit(ctx)
}
And that is my unit test for the handler.
func TestCreateTicketOptionHandler(t *testing.T) {
caseExpected, _ := json.Marshal(&ticket.Ticket{Id: 1, Name: "baris", Description: "test-desc", Allocation: 10})
srv := NewServer()
// expected := [][]byte{
// _, _ = json.Marshal(&ticket.Ticket{Id: 1, Name: "baris", Description: "test-desc", Allocation: 20}),
// // json.Marshal(&ticket.Ticket{Id: 1, Name: "baris", Description: "test-desc", Allocation: 20})
// }
tt := []struct {
name string
entry *ticket.Ticket
want []byte
code int
}{
{
"valid",
&ticket.Ticket{Name: "baris", Description: "test-desc", Allocation: 10},
caseExpected,
http.StatusOK,
},
}
var buf bytes.Buffer
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
json.NewEncoder(&buf).Encode(tc.entry)
req, err := http.NewRequest(http.MethodPost, "/ticket_options", &buf)
log.Println("1")
if err != nil {
log.Println("2")
t.Fatalf("could not create request: %v", err)
}
log.Println("3")
rec := httptest.NewRecorder()
log.Println("4")
srv.handleCreateTicketOption(rec, req)
log.Println("5")
if rec.Code != tc.code {
t.Fatalf("got status %d, want %v", rec.Code, tc.code)
}
log.Println("6")
if reflect.DeepEqual(rec.Body.Bytes(), tc.want) {
log.Println("7")
t.Fatalf("NAME:%v, got %v, want %v", tc.name, rec.Body.Bytes(), tc.want)
}
})
}
}
I did research about mocking pgx about most of them were testing the logic part not through the handler. I want to write unit test for both handler and logic itself seperately. However, the unit test I've written for the handler panics as below
github.com/bariis/gowit-case-study/psql.(*TicketService).CreateTicketOption(0xc000061348, {0x1485058, 0xc0000260c0}, {0x0, {0xc000026dd0, 0x5}, {0xc000026dd5, 0x9}, 0xa})
/Users/barisertas/workspace/gowit-case-study/psql/ticket.go:24 +0x125
github.com/bariis/gowit-case-study/http.(*Server).handleCreateTicketOption(0xc000061340, {0x1484bf0, 0xc000153280}, 0xc00018e000)
/Users/barisertas/workspace/gowit-case-study/http/ticket.go:77 +0x10b
github.com/bariis/gowit-case-study/http.TestCreateTicketOptionHandler.func2(0xc000119860)
/Users/barisertas/workspace/gowit-case-study/http/ticket_test.go:80 +0x305
psql/ticket.go:24: tx, err := t.db.dbPool.Begin(ctx)
http/ticket.go:77: ticket, err := s.TicketService.CreateTicketOption(r.Context(), t)
http/ticket_test.go:80: srv.handleCreateTicketOption(rec, req)
How can I mock this type of code?
Create an interface which has the required DB functions
Your DB handler implements this interface. You use the handler in actual execution
Create a mock handler using testify/mock and use this in place of DB handler in test cases
From what I can read, you have the following structure:
type Server struct {
TicketService ticket.Service
}
type TicketService struct {
db *sql.Db // ..or similar
}
func (ts *TicketService) CreateTicketOption(...)
The trick to mock this is by ensuring ticket.Service is an interface instead of a struct.
Like this:
type TicketService interface {
CreateTicketOption(ctx context.Context, ticket ticket.Ticket) (*ticket.Ticket, error) {
}
By doing this, your Server expects a TicketService interface.
Then you could do this:
type postgresTicketService struct {
db *sql.Db
}
func (pst *postgresTicketService) CreateTicketOption(...)...
Which means that the postgresTicketService satisfies the requirements to be passed as a ticket.Service to the Server.
This also means that you can do this:
type mockTicketService struct {
}
func (mts *mockTicketService) CreateTicketOption(...)...
This way you decouple the Server from the actual implementation, and you could just init the Server with the mockTicketService when testing and postgresTicketService when deploying.
I am new to golang and I am using an interactive prompt called promptui (https://github.com/manifoldco/promptui) in a project of mine. I have written several unit tests for this project already but I am struggling with how I would unit test this particular package that requires an input.
For example, How would I go about testing the following lines of code (encapsulated in a function):
func setEmail() string {
prompt := promptui.Prompt{Label: "Input your Email",
Validate: emailValidations,
}
email, err := prompt.Run()
if err != nil {
color.red("failed getting email")
os.exit(3)
}
return email
}
I think I need to somehow mock stdin but can't figure out the best way to do that within a test.
You should not try to test promptui as it is expected to be tested by its author.
What you can test:
You send correct parameters when you create promptui.Prompt
You use that promptui.Prompt in your code
You properly handle promptui.Prompt results
As you can see, all these tests does not verify if promptui.Prompt works correctly inside.
Tests #2 and #3 could be combined. You need to run you code against mock and if you got correct result, you can believe that both #2 and #3 are correct.
Create mock:
type Runner interface {
Run() (int, string, error)
}
type promptMock struct {
// t is not required for this test, but it is would be helpful to assert input parameters if we have it in Run()
t *testing.T
}
func (p promptMock) Run() (int, string, error) {
// return expected result
return 1, "", nil
}
You will need separate mock for testing error flow.
Update your code to inject mock:
func setEmail(runner Runner) string {
email, err := runner.Run()
if err != nil {
color.red("failed getting email")
os.exit(3)
}
return email
}
Now it is testable.
Create function that creates prompt:
func getRunner() promptui.Prompt {
return promptui.Prompt{Label: "Input your Email",
Validate: emailValidations,
}
}
Write simple assert test to verify that we create correct structure.
The only not tested line will be setEmail(getRunner()) but it is trivial and can be covered by other types of tests.
For whatever reason, they don't export their stdin interface (https://github.com/manifoldco/promptui/blob/master/prompt.go#L49), so you can't mock it out, but you can directly mock os.Stdin and prefill it with whatever you need for testing. Though I agree with #Adrian, it has its own tests, so this shouldn't be necessary.
Extracted and refactored/simplified from source: Fill os.Stdin for function that reads from it
Refactored this way, it can be used for any function that reads from os.Stdin and expects a specific string.
Playground link: https://play.golang.org/p/rjgcGIaftBK
func TestSetEmail(t *testing.T) {
if err := TestExpectedStdinFunc("email#test.com", setEmail); err != nil {
t.Error(err)
return
}
fmt.Println("success")
}
func TestExpectedStdinFunc(expected string, f func() string) error {
content := []byte(expected)
tmpfile, err := ioutil.TempFile("", "example")
if err != nil {
return err
}
defer os.Remove(tmpfile.Name()) // clean up
if _, err := tmpfile.Write(content); err != nil {
return err
}
if _, err := tmpfile.Seek(0, 0); err != nil {
return err
}
oldStdin := os.Stdin
defer func() { os.Stdin = oldStdin }() // Restore original Stdin
os.Stdin = tmpfile
actual := f()
if actual != expected {
return errors.New(fmt.Sprintf("test failed, exptected: %s actual: %s", expected, actual))
}
if err := tmpfile.Close(); err != nil {
return err
}
return nil
}
promptui now has the Stdin property.
There is a fiddle here: https://play.golang.org/p/-mSgjY2kAw-
Here is our function that we will be testing:
func mock(p promptui.Prompt) string {
p.Label = "[Y/N]"
user_input, err := p.Run()
if err != nil {
fmt.Printf("Prompt failed %v\n", err)
}
return user_input
}
We need to create p, which will be an instance of promptui.Prompt and have a custom Stdin.
I got some help here - https://groups.google.com/g/golang-nuts/c/J-Y4LtdGNSw?pli=1 - in how to make a custom Stdin value, which simply has to conform to io.ReadCloser.
type ClosingBuffer struct {
*bytes.Buffer
}
func (cb ClosingBuffer) Close() error {
return nil
}
And then you use that as Stdin in the reader:
func TestMock(t *testing.T) {
reader := ClosingBuffer{
bytes.NewBufferString("N\n"),
}
p := promptui.Prompt{
Stdin: reader,
}
response := mock(p)
if !strings.EqualFold(response, "N") {
t.Errorf("nope!")
}
//t.Errorf(response)
}
edit: The above doesn't work for multiple prompts within the same function, as discussed here with a solution: https://github.com/manifoldco/promptui/issues/63 - "promptui internally uses a buffer of 4096 bytes. This means that you must pad your buffer or promptui will raise EOF."
I took this pad() function from that exchange - https://github.com/sandokandias/capiroto/blob/master/cmd/capiroto/main.go:
func pad(siz int, buf *bytes.Buffer) {
pu := make([]byte, 4096-siz)
for i := 0; i < 4096-siz; i++ {
pu[i] = 97
}
buf.Write(pu)
}
Then the test - - this solution uses ioutil.NopCloser rather than creating a new struct:
func TestMock(t *testing.T) {
i1 := "N\n"
i2 := "Y\n"
b := bytes.NewBuffer([]byte(i1))
pad(len(i1), b)
reader := ioutil.NopCloser(
b,
)
b.WriteString(i2)
pad(len(i2), b)
p := promptui.Prompt{
Stdin: reader,
}
response := mock(p)
if !strings.EqualFold(response, "NY") {
t.Errorf("nope!")
t.Errorf(response)
}
}
and the function we are testing:
func mock(p promptui.Prompt) string {
p.Label = "[Y/N]"
user_input, err := p.Run()
if err != nil {
fmt.Printf("Prompt failed %v\n", err)
}
user_input2, err := p.Run()
return user_input + user_input2
}
The fiddle for multiple prompts is here: https://play.golang.org/p/ElPysYq8aM1