go-sqlmock icon indicating copy to clipboard operation
go-sqlmock copied to clipboard

Mock time.Now()

Open gmhafiz opened this issue 4 years ago • 4 comments

I have a function with

func (r *repository) Update(ctx context.Context, book *models.Book) error {
	now := time.Now()

	_, err := r.db.ExecContext(ctx, UPDATE books set title = $1, description = $2, published_date = $3, image_url = $4, updated_at = $5 where book_id = $6, book.Title, book.Description,
		book.PublishedDate, book.ImageURL, now, book.BookID)
	if err != nil {
		return err
	}

	return nil
}

that I tried to unit test with

func TestRepository_Update(t *testing.T) {
	db, mock := NewMock()
	repo := New(db)

	mockBook := &models.Book{
		BookID:        1,
		Title:         "test1",
		PublishedDate: timeWant(),
		ImageURL: null.String{
			String: "https://example.com/image.png",
			Valid:  true,
		},
		Description: "test1",
	}

	mock.ExpectExec("UPDATE books set title").
		WithArgs(mockBook.Title, mockBook.Description, mockBook.PublishedDate, mockBook.ImageURL.String, time.Now().String(), mockBook.BookID).
		WillReturnResult(sqlmock.NewErrorResult(nil))

	err := repo.Update(context.Background(), mockBook)

	assert.NoError(t, err)
}

The fails obviously because the value of time.Now() in the test and in the implementation are different.

=== RUN   TestRepository_Update
    postgres_test.go:189: 
        	Error Trace:	postgres_test.go:189
        	Error:      	Received unexpected error:
        	            	ExecQuery 'UPDATE books set title = $1, description = $2, published_date = $3, image_url = $4, updated_at = $5 where book_id = $6', arguments do not match: argument 4 expected [string - 2021-05-26 22:07:58.847659183 +1000 AEST m=+0.001800825] does not match actual [time.Time - 2021-05-26 22:07:58.847710743 +1000 AEST m=+0.001852395]
        	Test:       	TestRepository_Update
--- FAIL: TestRepository_Update (0.00s)

How do you write unit a test when the implementation has a value that will change like time.Now() or somehow mock time.Now()

For reference, book struct

type Book struct {
	BookID        int64       `db:"book_id" json:"book_id"`
	Title         string      `db:"title" json:"title"`
	PublishedDate time.Time   `db:"published_date" json:"published_date"`
	ImageURL      null.String `db:"image_url" json:"image_url,`
	Description   string      `db:"description" json:"description"`
	CreatedAt     null.Time   `db:"created_at" json:"created_at,`
	UpdatedAt     null.Time   `db:"updated_at" json:"updated_at,`
	DeletedAt     null.Time   `db:"deleted_at" json:"deleted_at,`
}

gmhafiz avatar May 26 '21 12:05 gmhafiz

Have you read the api docs of sqlmock before asking?

l3pp4rd avatar May 28 '21 04:05 l3pp4rd

@gmhafiz check Customize SQL query matching chapter of https://pkg.go.dev/github.com/DATA-DOG/go-sqlmock?utm_source=godoc

egregors avatar Aug 24 '21 12:08 egregors

https://pkg.go.dev/github.com/DATA-DOG/go-sqlmock#readme-matching-arguments-like-time-time

linnaname avatar Sep 28 '21 07:09 linnaname

Add this into your test file

type AnyTime struct{}

func (a AnyTime) Match(v driver.Value) bool {
	_, ok := v.(time.Time)
	return ok
}

and replace your data time.now() with AnyTime{}

mock.ExpectExec("UPDATE books set title").
		WithArgs(mockBook.Title, mockBook.Description, mockBook.PublishedDate, mockBook.ImageURL.String, AnyTime{}, mockBook.BookID).
		WillReturnResult(sqlmock.NewErrorResult(nil))

ibadi-id avatar Mar 05 '22 15:03 ibadi-id