package exec import ( "database/sql" "encoding/json" "testing" "github.com/DATA-DOG/go-sqlmock" "github.com/stretchr/testify/suite" ) type scannerSuite struct { suite.Suite } func TestScanner(t *testing.T) { suite.Run(t, &scannerSuite{}) } func (s *scannerSuite) TestScanStructs() { type StructWithTags struct { Address string `db:"address"` Name string `db:"name"` } db, mock, err := sqlmock.New() s.Require().NoError(err) mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). WillReturnRows(sqlmock.NewRows([]string{"address", "name"}). AddRow(testAddr1, testName1). AddRow(testAddr2, testName2), ) rows, err := db.Query(`SELECT * FROM "items"`) s.Require().NoError(err) sc := NewScanner(rows) result := make([]StructWithTags, 0) err = sc.ScanStructs(result) s.Require().EqualError(err, errUnsupportedScanStructsType.Error()) err = sc.ScanStructs(&result) s.Require().NoError(err) s.Require().ElementsMatch( []StructWithTags{{Address: testAddr1, Name: testName1}, {Address: testAddr2, Name: testName2}}, result, ) } func (s *scannerSuite) TestScanVals() { db, mock, err := sqlmock.New() s.Require().NoError(err) mock.ExpectQuery(`SELECT "id" FROM "items"`). WithArgs(). WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1).AddRow(2)) rows, err := db.Query(`SELECT "id" FROM "items"`) s.Require().NoError(err) sc := NewScanner(rows) result := make([]int, 0) err = sc.ScanVals(result) s.Require().EqualError(err, errUnsupportedScanValsType.Error()) err = sc.ScanVals(&result) s.Require().NoError(err) s.Require().ElementsMatch([]int{1, 2}, result) } func (s *scannerSuite) TestGetRecords() { db, mock, err := sqlmock.New() s.Require().NoError(err) mock.ExpectQuery("SELECT \\* FROM `items`"). WithArgs(). WillReturnRows( sqlmock.NewRows([]string{"address", "name"}). AddRow("111 Test Addr", "Test1"). AddRow("111 Test Addr", "Test1"), ) rows, err := db.Query("SELECT * FROM `items`") s.Require().NoError(err) result, err := NewScanner(rows).GetRecords() s.Require().NoError(err) s.Equal([]map[string]any{ {"address": "111 Test Addr", "name": "Test1"}, {"address": "111 Test Addr", "name": "Test1"}, }, result) } func (s *scannerSuite) TestScanVal() { db, mock, err := sqlmock.New() s.Require().NoError(err) mock.ExpectQuery(`SELECT "id" FROM "items"`). WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(42)) rows, err := db.Query(`SELECT "id" FROM "items"`) s.Require().NoError(err) sc := NewScanner(rows) s.True(sc.Next()) var id int64 err = sc.ScanVal(&id) s.Require().NoError(err) s.Equal(int64(42), id) } func (s *scannerSuite) TestScanVal_withRawBytes() { db, mock, err := sqlmock.New() s.Require().NoError(err) mock.ExpectQuery(`SELECT "data" FROM "items"`). WillReturnRows(sqlmock.NewRows([]string{"data"}).AddRow([]byte(testByteSliceContent))) rows, err := db.Query(`SELECT "data" FROM "items"`) s.Require().NoError(err) sc := NewScanner(rows) s.True(sc.Next()) var rb sql.RawBytes err = sc.ScanVal(&rb) s.Require().NoError(err) _ = sc.Close() // 关闭后缓冲区应已拷贝,值仍然有效 s.Equal(sql.RawBytes(testByteSliceContent), rb) } func (s *scannerSuite) TestScanVal_withByteSlice() { db, mock, err := sqlmock.New() s.Require().NoError(err) mock.ExpectQuery(`SELECT "data" FROM "items"`). WillReturnRows(sqlmock.NewRows([]string{"data"}).AddRow(testByteSliceContent)) rows, err := db.Query(`SELECT "data" FROM "items"`) s.Require().NoError(err) sc := NewScanner(rows) s.True(sc.Next()) var b []byte err = sc.ScanVal(&b) s.Require().NoError(err) s.Equal([]byte(testByteSliceContent), b) } func (s *scannerSuite) TestScanVal_withByteSlice_complexJSON() { db, mock, err := sqlmock.New(sqlmock.ValueConverterOption(anyValueConverter{})) s.Require().NoError(err) payload := map[string]interface{}{"key": "val", "num": float64(1)} mock.ExpectQuery(`SELECT "data" FROM "items"`). WillReturnRows(mock.NewRows([]string{"data"}).AddRow(payload)) rows, err := db.Query(`SELECT "data" FROM "items"`) s.Require().NoError(err) sc := NewScanner(rows) s.True(sc.Next()) var b []byte err = sc.ScanVal(&b) s.Require().NoError(err) var got map[string]interface{} s.Require().NoError(json.Unmarshal(b, &got)) s.Equal(payload, got) } func (s *scannerSuite) TestScanVal_withStruct() { type DocItem struct { Title string `json:"title"` Score int `json:"score"` } db, mock, err := sqlmock.New(sqlmock.ValueConverterOption(anyValueConverter{})) s.Require().NoError(err) mock.ExpectQuery(`SELECT "doc" FROM "items"`). WillReturnRows(mock.NewRows([]string{"doc"}). AddRow(map[string]interface{}{"title": "hello", "score": float64(99)})) rows, err := db.Query(`SELECT "doc" FROM "items"`) s.Require().NoError(err) sc := NewScanner(rows) s.True(sc.Next()) var doc DocItem err = sc.ScanVal(&doc) s.Require().NoError(err) s.Equal(DocItem{Title: "hello", Score: 99}, doc) } func (s *scannerSuite) TestScanVal_withStruct_null() { type DocItem struct { Title string `json:"title"` } db, mock, err := sqlmock.New() s.Require().NoError(err) mock.ExpectQuery(`SELECT "doc" FROM "items"`). WillReturnRows(sqlmock.NewRows([]string{"doc"}).AddRow(nil)) rows, err := db.Query(`SELECT "doc" FROM "items"`) s.Require().NoError(err) sc := NewScanner(rows) s.True(sc.Next()) var doc DocItem err = sc.ScanVal(&doc) s.Require().NoError(err) s.Equal(DocItem{}, doc) } func (s *scannerSuite) TestScanVal_withStruct_sqlScanner() { db, mock, err := sqlmock.New() s.Require().NoError(err) mock.ExpectQuery(`SELECT "name" FROM "items"`). WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("hello")) rows, err := db.Query(`SELECT "name" FROM "items"`) s.Require().NoError(err) sc := NewScanner(rows) s.True(sc.Next()) var ns sql.NullString err = sc.ScanVal(&ns) s.Require().NoError(err) s.Equal(sql.NullString{String: "hello", Valid: true}, ns) }