Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

It is very hard to pinpoint _which_ expectation failed #210

Open
kennytm opened this issue Dec 11, 2019 · 0 comments
Open

It is very hard to pinpoint _which_ expectation failed #210

kennytm opened this issue Dec 11, 2019 · 0 comments

Comments

@kennytm
Copy link

kennytm commented Dec 11, 2019

When an expectation failed, the SQL request would error suggesting the type of the actual expectation. However, it is not detailed enough to show exactly which expectation failed. Consider the following simplified example.

Example code
package main

import (
	"database/sql"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
)

func InsertValues(db *sql.DB, table string, column string, values ...interface{}) (err error) {
	var tx *sql.Tx
	tx, err = db.Begin()
	if err != nil {
		return
	}
	defer func() {
		if err != nil {
			tx.Rollback()
		} else {
			err = tx.Commit()
		}
	}()

	stmt := "INSERT INTO " + table + " (" + column + ") VALUES (?)"
	for _, val := range values {
		if _, err = tx.Exec(stmt, val); err != nil {
			return
		}
	}
	return
}

func TestInsertValues(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("cannot create sql mock: %s", err)
	}

	mock.ExpectBegin()
	mock.ExpectExec(`INSERT INTO tbl`).WithArgs(2).WillReturnResult(sqlmock.NewResult(1, 1))
	mock.ExpectCommit()

	mock.ExpectBegin()
	mock.ExpectExec(`INSERT INTO tbl`).WithArgs(3).WillReturnResult(sqlmock.NewResult(2, 1))
	mock.ExpectCommit()

	mock.ExpectBegin()
	mock.ExpectExec(`INSERT INTO tbl`).WithArgs(4).WillReturnResult(sqlmock.NewResult(3, 1))
	mock.ExpectCommit()

	err = InsertValues(db, "tbl", "col", 2, 3, 4)
	if err != nil {
		t.Errorf("cannot insert values: %s", err)
	}
	err = mock.ExpectationsWereMet()
	if err != nil {
		t.Errorf("unfulfilled expectation: %s", err)
	}
}

The code would fail like this:

--- FAIL: TestInsertValues (0.00s)
    1_test.go:53: cannot insert values: call to ExecQuery 'INSERT INTO tbl (col) VALUES (?)' with args [{Name: Ordinal:1 Value:3}], was not expected, next expectation is: ExpectedCommit => expecting transaction Commit
    1_test.go:57: unfulfilled expectation: there is a remaining expectation which was not matched: ExpectedCommit => expecting transaction Commit
FAIL
FAIL    command-line-arguments  0.240s
FAIL

We know that the error happens because an ExpectCommit is not fulfilled, but it does not suggest which of the three ExpectCommits failed! This makes debugging test failure difficult.


I propose that

  1. Every call to ExpectXXX() should record the stack trace, e.g. with the stack trace we would know it is the ExpectCommit() on line 41 causing the failure. And/or
  2. Add a WithComment(string)/WithCommentf(string, ...interface{}) method to every ExpectedXXX type. The expectation failure would include the comment.

WithComment is needed to distinguish between different Expect invocations in a loop (where the stack trace would still be ambiguous) e.g.

for i, val := range values {
    comment := fmt.Sprintf("iteration #%d, value %s", i, val)
    mock.ExpectBegin().WithComment(comment)
    mock.ExpectExec("INSERT").WithComment(comment).WithArgs(val).WillReturnResult(sqlmock.NewResult(int64(i+1), 1))
    mock.ExpectCommit().WithComment(comment)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant