1
0
mirror of https://github.com/DATA-DOG/go-sqlmock.git synced 2024-11-21 17:17:08 +02:00
go-sqlmock/rows_test.go

794 lines
20 KiB
Go

package sqlmock
import (
"bytes"
"database/sql"
"database/sql/driver"
"fmt"
"testing"
)
const invalid = `☠☠☠ MEMORY OVERWRITTEN ☠☠☠ `
func ExampleRows() {
db, mock, err := New()
if err != nil {
fmt.Println("failed to open sqlmock database:", err)
}
defer db.Close()
rows := NewRows([]string{"id", "title"}).
AddRow(1, "one").
AddRow(2, "two")
mock.ExpectQuery("SELECT").WillReturnRows(rows)
rs, _ := db.Query("SELECT")
defer rs.Close()
for rs.Next() {
var id int
var title string
rs.Scan(&id, &title)
fmt.Println("scanned id:", id, "and title:", title)
}
if rs.Err() != nil {
fmt.Println("got rows error:", rs.Err())
}
// Output: scanned id: 1 and title: one
// scanned id: 2 and title: two
}
func ExampleRows_rowError() {
db, mock, err := New()
if err != nil {
fmt.Println("failed to open sqlmock database:", err)
}
defer db.Close()
rows := NewRows([]string{"id", "title"}).
AddRow(0, "one").
AddRow(1, "two").
RowError(1, fmt.Errorf("row error"))
mock.ExpectQuery("SELECT").WillReturnRows(rows)
rs, _ := db.Query("SELECT")
defer rs.Close()
for rs.Next() {
var id int
var title string
rs.Scan(&id, &title)
fmt.Println("scanned id:", id, "and title:", title)
}
if rs.Err() != nil {
fmt.Println("got rows error:", rs.Err())
}
// Output: scanned id: 0 and title: one
// got rows error: row error
}
func ExampleRows_closeError() {
db, mock, err := New()
if err != nil {
fmt.Println("failed to open sqlmock database:", err)
}
defer db.Close()
rows := NewRows([]string{"id", "title"}).CloseError(fmt.Errorf("close error"))
mock.ExpectQuery("SELECT").WillReturnRows(rows)
rs, _ := db.Query("SELECT")
// Note: that close will return error only before rows EOF
// that is a default sql package behavior. If you run rs.Next()
// it will handle the error internally and return nil bellow
if err := rs.Close(); err != nil {
fmt.Println("got error:", err)
}
// Output: got error: close error
}
func ExampleRows_rawBytes() {
db, mock, err := New()
if err != nil {
fmt.Println("failed to open sqlmock database:", err)
}
defer db.Close()
rows := NewRows([]string{"id", "binary"}).
AddRow(1, []byte(`one binary value with some text!`)).
AddRow(2, []byte(`two binary value with even more text than the first one`))
mock.ExpectQuery("SELECT").WillReturnRows(rows)
rs, _ := db.Query("SELECT")
defer rs.Close()
type scanned struct {
id int
raw sql.RawBytes
}
fmt.Println("initial read...")
var ss []scanned
for rs.Next() {
var s scanned
rs.Scan(&s.id, &s.raw)
ss = append(ss, s)
fmt.Println("scanned id:", s.id, "and raw:", string(s.raw))
}
if rs.Err() != nil {
fmt.Println("got rows error:", rs.Err())
}
fmt.Println("after reading all...")
for _, s := range ss {
fmt.Println("scanned id:", s.id, "and raw:", string(s.raw))
}
// Output:
// initial read...
// scanned id: 1 and raw: one binary value with some text!
// scanned id: 2 and raw: two binary value with even more text than the first one
// after reading all...
// scanned id: 1 and raw: ☠☠☠ MEMORY OVERWRITTEN ☠
// scanned id: 2 and raw: ☠☠☠ MEMORY OVERWRITTEN ☠☠☠ ☠☠☠ MEMORY
}
func ExampleRows_expectToBeClosed() {
db, mock, err := New()
if err != nil {
fmt.Println("failed to open sqlmock database:", err)
}
defer db.Close()
rows := NewRows([]string{"id", "title"}).AddRow(1, "john")
mock.ExpectQuery("SELECT").WillReturnRows(rows).RowsWillBeClosed()
db.Query("SELECT")
if err := mock.ExpectationsWereMet(); err != nil {
fmt.Println("got error:", err)
}
// Output: got error: expected query rows to be closed, but it was not: ExpectedQuery => expecting Query, QueryContext or QueryRow which:
// - matches sql: 'SELECT'
// - is without arguments
// - should return rows:
// row 0 - [1 john]
}
func ExampleRows_customDriverValue() {
db, mock, err := New()
if err != nil {
fmt.Println("failed to open sqlmock database:", err)
}
defer db.Close()
rows := NewRows([]string{"id", "null_int"}).
AddRow(1, 7).
AddRow(5, sql.NullInt64{Int64: 5, Valid: true}).
AddRow(2, sql.NullInt64{})
mock.ExpectQuery("SELECT").WillReturnRows(rows)
rs, _ := db.Query("SELECT")
defer rs.Close()
for rs.Next() {
var id int
var num sql.NullInt64
rs.Scan(&id, &num)
fmt.Println("scanned id:", id, "and null int64:", num)
}
if rs.Err() != nil {
fmt.Println("got rows error:", rs.Err())
}
// Output: scanned id: 1 and null int64: {7 true}
// scanned id: 5 and null int64: {5 true}
// scanned id: 2 and null int64: {0 false}
}
func TestAllowsToSetRowsErrors(t *testing.T) {
t.Parallel()
db, mock, err := New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
rows := NewRows([]string{"id", "title"}).
AddRow(0, "one").
AddRow(1, "two").
RowError(1, fmt.Errorf("error"))
mock.ExpectQuery("SELECT").WillReturnRows(rows)
rs, err := db.Query("SELECT")
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
defer rs.Close()
if !rs.Next() {
t.Fatal("expected the first row to be available")
}
if rs.Err() != nil {
t.Fatalf("unexpected error: %s", rs.Err())
}
if rs.Next() {
t.Fatal("was not expecting the second row, since there should be an error")
}
if rs.Err() == nil {
t.Fatal("expected an error, but got none")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}
func TestRowsCloseError(t *testing.T) {
t.Parallel()
db, mock, err := New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
rows := NewRows([]string{"id"}).CloseError(fmt.Errorf("close error"))
mock.ExpectQuery("SELECT").WillReturnRows(rows)
rs, err := db.Query("SELECT")
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if err := rs.Close(); err == nil {
t.Fatal("expected a close error")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}
func TestRowsClosed(t *testing.T) {
t.Parallel()
db, mock, err := New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
rows := NewRows([]string{"id"}).AddRow(1)
mock.ExpectQuery("SELECT").WillReturnRows(rows).RowsWillBeClosed()
rs, err := db.Query("SELECT")
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if err := rs.Close(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}
func TestQuerySingleRow(t *testing.T) {
t.Parallel()
db, mock, err := New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
rows := NewRows([]string{"id"}).
AddRow(1).
AddRow(2)
mock.ExpectQuery("SELECT").WillReturnRows(rows)
var id int
if err := db.QueryRow("SELECT").Scan(&id); err != nil {
t.Fatalf("unexpected error: %s", err)
}
mock.ExpectQuery("SELECT").WillReturnRows(NewRows([]string{"id"}))
if err := db.QueryRow("SELECT").Scan(&id); err != sql.ErrNoRows {
t.Fatal("expected sql no rows error")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}
func TestQueryRowBytesInvalidatedByNext_bytesIntoRawBytes(t *testing.T) {
t.Parallel()
replace := []byte(invalid)
rows := NewRows([]string{"raw"}).
AddRow([]byte(`one binary value with some text!`)).
AddRow([]byte(`two binary value with even more text than the first one`))
scan := func(rs *sql.Rows) ([]byte, error) {
var raw sql.RawBytes
return raw, rs.Scan(&raw)
}
want := []struct {
Initial []byte
Replaced []byte
}{
{Initial: []byte(`one binary value with some text!`), Replaced: replace[:len(replace)-7]},
{Initial: []byte(`two binary value with even more text than the first one`), Replaced: bytes.Join([][]byte{replace, replace[:len(replace)-23]}, nil)},
}
queryRowBytesInvalidatedByNext(t, rows, scan, want)
}
func TestQueryRowBytesNotInvalidatedByNext_bytesIntoBytes(t *testing.T) {
t.Parallel()
rows := NewRows([]string{"raw"}).
AddRow([]byte(`one binary value with some text!`)).
AddRow([]byte(`two binary value with even more text than the first one`))
scan := func(rs *sql.Rows) ([]byte, error) {
var b []byte
return b, rs.Scan(&b)
}
want := [][]byte{[]byte(`one binary value with some text!`), []byte(`two binary value with even more text than the first one`)}
queryRowBytesNotInvalidatedByNext(t, rows, scan, want)
}
func TestQueryRowBytesNotInvalidatedByNext_stringIntoBytes(t *testing.T) {
t.Parallel()
rows := NewRows([]string{"raw"}).
AddRow(`one binary value with some text!`).
AddRow(`two binary value with even more text than the first one`)
scan := func(rs *sql.Rows) ([]byte, error) {
var b []byte
return b, rs.Scan(&b)
}
want := [][]byte{[]byte(`one binary value with some text!`), []byte(`two binary value with even more text than the first one`)}
queryRowBytesNotInvalidatedByNext(t, rows, scan, want)
}
func TestQueryRowBytesInvalidatedByClose_bytesIntoRawBytes(t *testing.T) {
t.Parallel()
replace := []byte(invalid)
rows := NewRows([]string{"raw"}).AddRow([]byte(`one binary value with some text!`))
scan := func(rs *sql.Rows) ([]byte, error) {
var raw sql.RawBytes
return raw, rs.Scan(&raw)
}
want := struct {
Initial []byte
Replaced []byte
}{
Initial: []byte(`one binary value with some text!`),
Replaced: replace[:len(replace)-7],
}
queryRowBytesInvalidatedByClose(t, rows, scan, want)
}
func TestQueryRowBytesNotInvalidatedByClose_bytesIntoBytes(t *testing.T) {
t.Parallel()
rows := NewRows([]string{"raw"}).AddRow([]byte(`one binary value with some text!`))
scan := func(rs *sql.Rows) ([]byte, error) {
var b []byte
return b, rs.Scan(&b)
}
queryRowBytesNotInvalidatedByClose(t, rows, scan, []byte(`one binary value with some text!`))
}
func TestQueryRowBytesNotInvalidatedByClose_stringIntoBytes(t *testing.T) {
t.Parallel()
rows := NewRows([]string{"raw"}).AddRow(`one binary value with some text!`)
scan := func(rs *sql.Rows) ([]byte, error) {
var b []byte
return b, rs.Scan(&b)
}
queryRowBytesNotInvalidatedByClose(t, rows, scan, []byte(`one binary value with some text!`))
}
func TestRowsScanError(t *testing.T) {
t.Parallel()
db, mock, err := New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
r := NewRows([]string{"col1", "col2"}).AddRow("one", "two").AddRow("one", nil)
mock.ExpectQuery("SELECT").WillReturnRows(r)
rs, err := db.Query("SELECT")
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
defer rs.Close()
var one, two string
if !rs.Next() || rs.Err() != nil || rs.Scan(&one, &two) != nil {
t.Fatal("unexpected error on first row scan")
}
if !rs.Next() || rs.Err() != nil {
t.Fatal("unexpected error on second row read")
}
err = rs.Scan(&one, &two)
if err == nil {
t.Fatal("expected an error for scan, but got none")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}
func TestCSVRowParser(t *testing.T) {
t.Parallel()
rs := NewRows([]string{"col1", "col2", "col3"}).FromCSVString("a,NULL,NULL")
db, mock, err := New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
mock.ExpectQuery("SELECT").WillReturnRows(rs)
rw, err := db.Query("SELECT")
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
defer rw.Close()
var col1 string
var col2 []byte
var col3 *string
rw.Next()
if err = rw.Scan(&col1, &col2, &col3); err != nil {
t.Fatalf("unexpected error: %s", err)
}
if col1 != "a" {
t.Fatalf("expected col1 to be 'a', but got [%T]:%+v", col1, col1)
}
if col2 != nil {
t.Fatalf("expected col2 to be nil, but got [%T]:%+v", col2, col2)
}
if col3 != nil {
t.Fatalf("expected col3 to be nil, but got [%T]:%+v", col3, col3)
}
}
func TestCSVParserInvalidInput(t *testing.T) {
defer func() {
recover()
}()
_ = NewRows([]string{"col1", "col2"}).FromCSVString("a,\"NULL\"\"")
// shouldn't reach here
t.Error("expected panic from parsing invalid CSV")
}
func TestWrongNumberOfValues(t *testing.T) {
// Open new mock database
db, mock, err := New()
if err != nil {
fmt.Println("error creating mock database")
return
}
defer db.Close()
defer func() {
recover()
}()
mock.ExpectQuery("SELECT ID FROM TABLE").WithArgs(101).WillReturnRows(NewRows([]string{"ID"}).AddRow(101, "Hello"))
db.Query("SELECT ID FROM TABLE", 101)
// shouldn't reach here
t.Error("expected panic from query")
}
func TestEmptyRowSets(t *testing.T) {
rs1 := NewRows([]string{"a"}).AddRow("a")
rs2 := NewRows([]string{"b"})
rs3 := NewRows([]string{"c"})
set1 := &rowSets{sets: []*Rows{rs1, rs2}}
set2 := &rowSets{sets: []*Rows{rs3, rs2}}
set3 := &rowSets{sets: []*Rows{rs2}}
if set1.empty() {
t.Fatalf("expected rowset 1, not to be empty, but it was")
}
if !set2.empty() {
t.Fatalf("expected rowset 2, to be empty, but it was not")
}
if !set3.empty() {
t.Fatalf("expected rowset 3, to be empty, but it was not")
}
}
func queryRowBytesInvalidatedByNext(t *testing.T, rows *Rows, scan func(*sql.Rows) ([]byte, error), want []struct {
Initial []byte
Replaced []byte
}) {
db, mock, err := New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
mock.ExpectQuery("SELECT").WillReturnRows(rows)
rs, err := db.Query("SELECT")
if err != nil {
t.Fatalf("failed to query rows: %s", err)
}
if !rs.Next() || rs.Err() != nil {
t.Fatal("unexpected error on first row retrieval")
}
var count int
for i := 0; ; i++ {
count++
b, err := scan(rs)
if err != nil {
t.Fatalf("unexpected error scanning row: %s", err)
}
if exp := want[i].Initial; !bytes.Equal(b, exp) {
t.Fatalf("expected raw value to be '%s' (len:%d), but got [%T]:%s (len:%d)", exp, len(exp), b, b, len(b))
}
next := rs.Next()
if exp := want[i].Replaced; !bytes.Equal(b, exp) {
t.Fatalf("expected raw value to be replaced with '%s' (len:%d) after calling Next(), but got [%T]:%s (len:%d)", exp, len(exp), b, b, len(b))
}
if !next {
break
}
}
if err := rs.Err(); err != nil {
t.Fatalf("row iteration failed: %s", err)
}
if exp := len(want); count != exp {
t.Fatalf("incorrect number of rows exp: %d, but got %d", exp, count)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}
func queryRowBytesNotInvalidatedByNext(t *testing.T, rows *Rows, scan func(*sql.Rows) ([]byte, error), want [][]byte) {
db, mock, err := New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
mock.ExpectQuery("SELECT").WillReturnRows(rows)
rs, err := db.Query("SELECT")
if err != nil {
t.Fatalf("failed to query rows: %s", err)
}
if !rs.Next() || rs.Err() != nil {
t.Fatal("unexpected error on first row retrieval")
}
var count int
for i := 0; ; i++ {
count++
b, err := scan(rs)
if err != nil {
t.Fatalf("unexpected error scanning row: %s", err)
}
if exp := want[i]; !bytes.Equal(b, exp) {
t.Fatalf("expected raw value to be '%s' (len:%d), but got [%T]:%s (len:%d)", exp, len(exp), b, b, len(b))
}
next := rs.Next()
if exp := want[i]; !bytes.Equal(b, exp) {
t.Fatalf("expected raw value to be replaced with '%s' (len:%d) after calling Next(), but got [%T]:%s (len:%d)", exp, len(exp), b, b, len(b))
}
if !next {
break
}
}
if err := rs.Err(); err != nil {
t.Fatalf("row iteration failed: %s", err)
}
if exp := len(want); count != exp {
t.Fatalf("incorrect number of rows exp: %d, but got %d", exp, count)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}
func queryRowBytesInvalidatedByClose(t *testing.T, rows *Rows, scan func(*sql.Rows) ([]byte, error), want struct {
Initial []byte
Replaced []byte
}) {
db, mock, err := New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
mock.ExpectQuery("SELECT").WillReturnRows(rows)
rs, err := db.Query("SELECT")
if err != nil {
t.Fatalf("failed to query rows: %s", err)
}
if !rs.Next() || rs.Err() != nil {
t.Fatal("unexpected error on first row retrieval")
}
b, err := scan(rs)
if err != nil {
t.Fatalf("unexpected error scanning row: %s", err)
}
if !bytes.Equal(b, want.Initial) {
t.Fatalf("expected raw value to be '%s' (len:%d), but got [%T]:%s (len:%d)", want.Initial, len(want.Initial), b, b, len(b))
}
if err := rs.Close(); err != nil {
t.Fatalf("unexpected error closing rows: %s", err)
}
if !bytes.Equal(b, want.Replaced) {
t.Fatalf("expected raw value to be replaced with '%s' (len:%d) after calling Next(), but got [%T]:%s (len:%d)", want.Replaced, len(want.Replaced), b, b, len(b))
}
if err := rs.Err(); err != nil {
t.Fatalf("row iteration failed: %s", err)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}
func queryRowBytesNotInvalidatedByClose(t *testing.T, rows *Rows, scan func(*sql.Rows) ([]byte, error), want []byte) {
db, mock, err := New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
mock.ExpectQuery("SELECT").WillReturnRows(rows)
rs, err := db.Query("SELECT")
if err != nil {
t.Fatalf("failed to query rows: %s", err)
}
if !rs.Next() || rs.Err() != nil {
t.Fatal("unexpected error on first row retrieval")
}
b, err := scan(rs)
if err != nil {
t.Fatalf("unexpected error scanning row: %s", err)
}
if !bytes.Equal(b, want) {
t.Fatalf("expected raw value to be '%s' (len:%d), but got [%T]:%s (len:%d)", want, len(want), b, b, len(b))
}
if err := rs.Close(); err != nil {
t.Fatalf("unexpected error closing rows: %s", err)
}
if !bytes.Equal(b, want) {
t.Fatalf("expected raw value to be replaced with '%s' (len:%d) after calling Next(), but got [%T]:%s (len:%d)", want, len(want), b, b, len(b))
}
if err := rs.Err(); err != nil {
t.Fatalf("row iteration failed: %s", err)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}
func TestAddRows(t *testing.T) {
t.Parallel()
db, mock, err := New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
values := [][]driver.Value{
{
1, "John",
},
{
2, "Jane",
},
{
3, "Peter",
},
{
4, "Emily",
},
}
rows := NewRows([]string{"id", "name"}).AddRows(values...)
mock.ExpectQuery("SELECT").WillReturnRows(rows).RowsWillBeClosed()
rs, _ := db.Query("SELECT")
defer rs.Close()
for rs.Next() {
var id int
var name string
rs.Scan(&id, &name)
fmt.Println("scanned id:", id, "and name:", name)
}
if rs.Err() != nil {
fmt.Println("got rows error:", rs.Err())
}
// Output: scanned id: 1 and title: John
// scanned id: 2 and title: Jane
// scanned id: 3 and title: Peter
// scanned id: 4 and title: Emily
}
func TestAddRowExpectPanic(t *testing.T) {
t.Parallel()
const expectedPanic = "Expected number of values to match number of columns: expected 1, actual 2"
values := []driver.Value{
"John",
"Jane",
}
defer func() {
if r := recover(); r != nil {
if r != expectedPanic {
t.Fatalf("panic message did not match expected: expected '%s', actual '%s'", r, expectedPanic)
}
return
}
t.Fatalf("expected panic: %s", expectedPanic)
}()
rows := NewRows([]string{"id", "name"})
// Note missing spread "..."
rows.AddRow(values)
}
func ExampleRows_AddRows() {
db, mock, err := New()
if err != nil {
fmt.Println("failed to open sqlmock database:", err)
}
defer db.Close()
values := [][]driver.Value{
{
1, "one",
},
{
2, "two",
},
}
rows := NewRows([]string{"id", "title"}).AddRows(values...)
mock.ExpectQuery("SELECT").WillReturnRows(rows)
rs, _ := db.Query("SELECT")
defer rs.Close()
for rs.Next() {
var id int
var title string
rs.Scan(&id, &title)
fmt.Println("scanned id:", id, "and title:", title)
}
if rs.Err() != nil {
fmt.Println("got rows error:", rs.Err())
}
// Output: scanned id: 1 and title: one
// scanned id: 2 and title: two
}