Search code examples
gogo-testinggocql

How to write unit test case for gocql


package main 

import "github.com/gocql/gocql"


var CassandraSession *gocql.Session

func getSelectQuery(ClientId, UID, UUID, tableName, selectColumns string, sess *gocql.Session) (*gocql.Query, error) {
    
    selectQuery := fmt.Sprintf(qTamplate, selectColumns, tableName)
        query := sess.Query(selectQuery, ClientId, UID).Consistency(gocql.One)
    err = query.Exec()
    if err != nil {
        return query, err
    }
    
    return query, nil
}


func GetSeglistFromCassandra(reqId string, dataMap MapUserRequest) (models.IsInSegmentResponse, error) {
    resutMap := models.IsInSegmentResponse{}
    tableName := fmt.Sprintf("getter_%v", cid[len(cid) - 1])
    cqlsh := CassandraSession
    selectColums := "cohortid, is_deleted, cohortype"
    selectQueryObj, err := getSelectQuery(dataMap.ClientId, dataMap.UID, dataMap.UUID, tableName, selectColums, cqlsh)
    if err != nil {
        return resutMap, err
    }
    var id, deleted, cohortType int
    iter := selectQueryObj.Iter()
    for iter.Scan(&id, &deleted, &cohortType) {
        if deleted == 0 {
            if cohortType == 3 || cohortType == 2 {
                resutMap.ListId = append(resutMap.ListId, id)
            } else {
                resutMap.SegIds = append(resutMap.SegIds, id)
            }
        }
    }
    if err := iter.Close(); err != nil {
       return resultMap, err
    }
    return resutMap, nil
}

How to write unit test case in golang for this function GetSeglistFromCassandra which is using gocql without connecting to actual database if we can mock the gocql session creation, queries and iteration with example?


Solution

  • Summary

    In my opinion, there is no way to write a unit test except for making an interface for gocql and its implementations. its implementations will be a wrapper struct for gocql and a mock for test.

    The reason why we should use an interface and its wrapper struct is gocql provides their concrete types such as gocql.Session.

    My suggestion

    I suggest my approach to you. For that, I make your codes more simpler to handle.

    simple.go

    package mockgocql
    
    import (
        "fmt"
    
        "github.com/gocql/gocql"
    )
    
    // SessionInterface allows gomock mock of gocql.Session
    type SessionInterface interface {
        Query(stmt string, values ...interface{}) QueryInterface
    }
    
    // QueryInterface allows gomock mock of gocql.Query
    type QueryInterface interface {
        Exec() error
        Consistency(c gocql.Consistency) QueryInterface
    }
    
    func simpleGetSelectQuery(ClientId, UID, UUID, tableName, selectColumns string, sessionInterface SessionInterface) (QueryInterface, error) {
        selectQuery := fmt.Sprintf(selectColumns, tableName)
        query := sessionInterface.Query(selectQuery, ClientId, UUID, UID).Consistency(gocql.One)
    
        err := query.Exec()
        if err != nil {
            return query, err
        }
    
        return query, nil
    }
    
    func simpleGetSeglistFromCassandra(cqlsh SessionInterface, reqId string) error {
        selectColums := "cohortid, is_deleted, cohortype"
        _, err := simpleGetSelectQuery("ClientId", "Uid", "Uuid", "tableName", selectColums, cqlsh)
        if err != nil {
            return err
        }
        return err
    }
    
    • Explanations
      • Simplified functions simpleGetSelectQuery and simpleGetSeglistFromCassandra
      • Interfaces SessionInterface and QueryInterface
      • And CassandraSession should be injected as SessionInterface in simpleGetSeglistFromCassandra
    • From now on, you use gocql through these interfaces. For that, you should make its implementation, first.

    impl.go

    package mockgocql
    
    import (
        "fmt"
    
        "github.com/gocql/gocql"
    )
    
    type SessionImpl struct {
        session *gocql.Session
    }
    
    type QueryImpl struct {
        query *gocql.Query
    }
    
    func (s *SessionImpl) Query(stmt string, values ...interface{}) QueryInterface {
        query := s.session.Query(stmt, values)
        return &QueryImpl{query: query}
    }
    
    func (q *QueryImpl) Exec() error {
        return fmt.Errorf("test")
    }
    
    func (q *QueryImpl) Consistency(c gocql.Consistency) QueryInterface {
        return q
    }
    
    • Explanations
      • These codes are implementations for the interface to use.
    • These are the wrapper struct for the original gocql
    • Making a wrapper is a little tiring but it helps us make our own features besides the original gocql features.
    • Also it makes it possible for us to make a mock

    mock.go

    package mockgocql
    
    import (
        "github.com/gocql/gocql"
    )
    
    type SessionMock struct {
    }
    
    type QueryMock struct {
    }
    
    func (s *SessionMock) Query(stmt string, values ...interface{}) QueryInterface {
        return &QueryMock{}
    }
    
    func (q *QueryMock) Exec() error {
        return nil
    }
    
    func (q *QueryMock) Consistency(c gocql.Consistency) QueryInterface {
        return q
    }
    
    • Explanations.
      • These codes are mock implementations for the interface
    • It is a mock we want.
    • we can make any mock implementation as we want at a test.

    simple_test.go

    package mockgocql
    
    import (
        "testing"
    
        "github.com/stretchr/testify/assert"
    )
    
    func Test_simpleGetSeglistFromCassandra(t *testing.T) {
        mockCassandra := SessionMock{}
        err := simpleGetSeglistFromCassandra(&mockCassandra, "test")
        assert.NoError(t, err)
    }
    
    
    • We can make a test with a mock.
    • If we want various scenarios of the tests, we can make mocks according to its scenario.

    Reference

    I found others' trials here. https://github.com/gocql/gocql/issues/415 It seems they do similar things but it can be helpful to you.