How to test database interactions in golang applications
22 Dec 2020

How to test database interactions in golang applications

Testing of functions with database interactions always was challenging. Recently, I found a wonderful library go-sqlmock which will simplify writing tests and mocking database queries in golang applications a lot.

And I want to share a short example of how to work with it.

First, we have to install it

go get github.com/DATA-DOG/go-sqlmock

We have this function with SQL query:

func MenuByNameAndLanguage(ctx context.Context, db *sql.DB, name string, langcode string) (*models.Menu, error) {
    result, err := db.Query("SELECT id, langcode, title, link__uri, view_sidemenu FROM menu_link_content_data WHERE menu_name=? AND langcode=?",
         name,
         langcode,
    )
    defer result.Close()
    
    var menuLinks []models.MenuLink
    for result.Next() {
         menuLink := models.MenuLink{}
         err = result.Scan(&menuLink.ID, &menuLink.Langcode, &menuLink.Title, &menuLink.URL, &menuLink.SideMenu)
         menuLinks = append(menuLinks, menuLink)    
    }
    menu := &models.Menu{
         Name: name,
         Links: menuLinks,
    }
    return menu, err
}

This function just getting menu links by menu name and language.

And now let’s test it.

We are going to test that MenuByNameAndLanguage function will return correct Menu structure.

package menu


import (
    "context"
    "testing"


    "github.com/DATA-DOG/go-sqlmock"
    "github.com/stretchr/testify/assert"
    "gitlab.mfb.io/user/graphql_server/models"
)


func TestShouldReturnCorrectMenu(t *testing.T) {

    // Creates sqlmock database connection and a mock to manage expectations.
    db, mock, err := sqlmock.New()

    if err != nil {
        t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
    }
    // Closes the database and prevents new queries from starting.
    defer db.Close()

    // Here we are creating rows in our mocked database.
    rows := sqlmock.NewRows([]string{"id", "langcode", "title", "link__uri", "view_sidemenu"}).
        AddRow(1, "en", "enTitle", "/en-link", "0").
        AddRow(2, "en", "enTitle2", "/en-link2", "0")

    // This is most important part in our test. Here, literally, we are altering SQL query from MenuByNameAndLanguage
    // function and replacing result with our expected result. 
    mock.ExpectQuery("^SELECT (.+) FROM menu_link_content_data*").
        WithArgs("main", "en").
        WillReturnRows(rows)


    ctx := context.TODO()

    // Calls MenuByNameAndLanguage with mocked database connection in arguments list. 
    menu, err := MenuByNameAndLanguage(ctx, db, "main", "en")

    // Here we just construction our expecting result.
    var menuLinks []models.MenuLink
    menuLink1 := models.MenuLink{
        ID:       1,
        Title:    "enTitle",
        Langcode: "en",
        URL:      "/en-link",
        SideMenu: "0",
    }
    menuLinks = append(menuLinks, menuLink1)

    menuLink2 := models.MenuLink{
        ID:       2,
        Title:    "enTitle2",
        Langcode: "en",
        URL:      "/en-link2",
        SideMenu: "0",
    }

    menuLinks = append(menuLinks, menuLink2)

    expectedMenu := &models.Menu{
        Name:  "main",
        Links: menuLinks,
    }
    
    // And, finally, let's check if result from MenuByNameAndLanguage equal with expected result.// Here I used Testify library (https://github.com/stretchr/testify).
    assert.Equal(t, expectedMenu, menu)
} 

As you see everything in this example was pretty straightforward.

For mo details, you can refer to GoDocs.

Related posts

One of The Easiest Ways to Host your Go Web App
5 Sep 2023

One of The Easiest Ways to Host your Go Web App

Hello! In this post, I will explain the cost-effective method I use to host my Go web applications with varying levels of complexity, all starting from as low as $5 per month. This method also allows to easy deploy and scale your golang application.

go hosting digitalocean docker
103 Early Hints in Go, or the new Way of How to Improve Performance of a Web Page written in Go
14 Nov 2022

103 Early Hints in Go, or the new Way of How to Improve Performance of a Web Page written in Go

Since Go 1.19 we can use a new 103 (Early Hints) http status code when we create web applications. Let’s figure out how and when this could help us. We are going to create a simple golang web server that servers some html content. One html page will be served with 103 header and another one without. After loading comparison we will see how early hints can improve page performance.

go golang performance
Example of how Golang generics minimize the amount of code you need to write
9 Jun 2022

Example of how Golang generics minimize the amount of code you need to write

I guess that almost everyone in the go community was exciting when Go 1.18 was released, especially because of generics. Some days ago I decided to try generics in the real-world application, by refactoring some of its pieces, related to a caching logic.

go generics redis cache
Concurrent Map Writing and Reading in Go, or how to deal with the data races.
16 Jul 2021

Concurrent Map Writing and Reading in Go, or how to deal with the data races.

This time, I will show you how to work with the maps in go effectively and prevent the occurrence of the data race errors. Data races happen when several goroutines access the same resource concurrently and at least one of the accesses is a write.

go concurrent map data race
Ristretto - the Most Performant Concurrent Cache Library for Go
2 Mar 2021

Ristretto - the Most Performant Concurrent Cache Library for Go

Recently, I discovered a surprisingly reliable memory caching solution, which I’m planning to use in all my further applications to increase performance. In this blog post, I will share some code examples of how you can integrate Ristretto caching library into your application.

go caching ristretto performance